xlwings-server 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xlwings_server/.env.template +145 -0
- xlwings_server/__init__.py +12 -0
- xlwings_server/_version.py +34 -0
- xlwings_server/auth/__init__.py +0 -0
- xlwings_server/auth/custom/__init__.py +26 -0
- xlwings_server/auth/entraid/__init__.py +131 -0
- xlwings_server/auth/entraid/jwks.py +10 -0
- xlwings_server/azure_functions_templates/.funcignore +28 -0
- xlwings_server/azure_functions_templates/function_app.py +28 -0
- xlwings_server/azure_functions_templates/host.json +22 -0
- xlwings_server/azure_functions_templates/local.settings.json +8 -0
- xlwings_server/build_utils/__init__.py +9 -0
- xlwings_server/build_utils/static_file_hasher.py +212 -0
- xlwings_server/cli.py +1592 -0
- xlwings_server/config.py +228 -0
- xlwings_server/custom_functions/__init__.py +8 -0
- xlwings_server/custom_functions/examples.py +177 -0
- xlwings_server/custom_scripts/__init__.py +8 -0
- xlwings_server/custom_scripts/examples.py +94 -0
- xlwings_server/databases.py +19 -0
- xlwings_server/dependencies.py +126 -0
- xlwings_server/docker_templates/.dockerignore +15 -0
- xlwings_server/docker_templates/Dockerfile +60 -0
- xlwings_server/docker_templates/docker-compose.yaml +32 -0
- xlwings_server/hotreload.py +59 -0
- xlwings_server/main.py +242 -0
- xlwings_server/models/__init__.py +14 -0
- xlwings_server/models/user.py +53 -0
- xlwings_server/object_handles.py +142 -0
- xlwings_server/routers/__init__.py +0 -0
- xlwings_server/routers/manifest.py +82 -0
- xlwings_server/routers/root.py +16 -0
- xlwings_server/routers/socketio.py +69 -0
- xlwings_server/routers/taskpane.py +12 -0
- xlwings_server/routers/xlwings.py +197 -0
- xlwings_server/security_headers.json +53 -0
- xlwings_server/serializers/__init__.py +25 -0
- xlwings_server/serializers/default_serializer.py +19 -0
- xlwings_server/serializers/dictionary_serializer.py +25 -0
- xlwings_server/serializers/framework.py +50 -0
- xlwings_server/serializers/numpy_serializer.py +26 -0
- xlwings_server/serializers/pandas_serializer.py +95 -0
- xlwings_server/static/css/core.css +28 -0
- xlwings_server/static/css/style.css +0 -0
- xlwings_server/static/images/favicon.png +0 -0
- xlwings_server/static/images/xlwings-16.png +0 -0
- xlwings_server/static/images/xlwings-32.png +0 -0
- xlwings_server/static/images/xlwings-64.png +0 -0
- xlwings_server/static/images/xlwings-80.png +0 -0
- xlwings_server/static/js/auth.js +13 -0
- xlwings_server/static/js/config.js +4 -0
- xlwings_server/static/js/core/alpinejs-csp-boilerplate.js +11 -0
- xlwings_server/static/js/core/bootstrap-customizations.js +7 -0
- xlwings_server/static/js/core/custom-functions-code.js +296 -0
- xlwings_server/static/js/core/examples.js +62 -0
- xlwings_server/static/js/core/hotreload.js +3 -0
- xlwings_server/static/js/core/htmx-handlers.js +86 -0
- xlwings_server/static/js/core/officejs-history-fix-part1.js +3 -0
- xlwings_server/static/js/core/officejs-history-fix-part2.js +2 -0
- xlwings_server/static/js/core/reload-custom-functions.js +79 -0
- xlwings_server/static/js/core/socketio-handlers.js +34 -0
- xlwings_server/static/js/core/xlwings-alert.js +22 -0
- xlwings_server/static/js/core/xlwingsjs/alert.js +85 -0
- xlwings_server/static/js/core/xlwingsjs/auth.js +63 -0
- xlwings_server/static/js/core/xlwingsjs/sheet-buttons.js +133 -0
- xlwings_server/static/js/core/xlwingsjs/utils.js +119 -0
- xlwings_server/static/js/core/xlwingsjs/wasm.js +131 -0
- xlwings_server/static/js/core/xlwingsjs/xlwings.js +1060 -0
- xlwings_server/static/js/main.js +0 -0
- xlwings_server/static/js/ribbon.js +17 -0
- xlwings_server/static/vendor/@alpinejs/LICENSE +21 -0
- xlwings_server/static/vendor/@alpinejs/csp/dist/cdn.min.js +7 -0
- xlwings_server/static/vendor/@microsoft/office-js/LICENSE.md +76 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/af-za/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/agaveerrorux.js +18 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/agavedefaulticon32x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/agavedefaulticon96x96.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/businessbarclose_16x16x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/dropdownarrow_16x16x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/ellipsis_16x16x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/miniinfoblue_16x16x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/moe_default_icon.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/moe_status_icons.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/office.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/images/refresh_16x16x32.png +0 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/index.html +16 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/agaveerrorux/style/agaveerrorux.css +482 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/am-et/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-ae/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-bh/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-dz/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-eg/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-iq/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-jo/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-kw/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-lb/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-ly/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-ma/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-om/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-qa/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-sa/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-sy/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-tn/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ar-ye/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ariatelemetry/aria-web-telemetry-2.8.0.min.js +2 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ariatelemetry/aria-web-telemetry-2.9.0.min.js +2 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ariatelemetry/aria-web-telemetry.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/az-latn-az/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/be-by/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/bg-bg/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/bn-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/bs-latn-ba/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ca-es/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/cs-cz/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/cy-gb/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/da-dk/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/de-at/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/de-ch/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/de-de/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/de-li/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/de-lu/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/el-gr/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-029/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-au/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-bz/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-ca/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-gb/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-ie/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-in/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-jm/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-my/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-nz/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-ph/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-sg/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-tt/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-us/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-za/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/en-zw/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-ar/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-bo/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-cl/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-co/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-cr/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-do/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-ec/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-es/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-gt/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-hn/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-mx/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-ni/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-pa/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-pe/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-pr/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-py/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-sv/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-us/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-uy/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es-ve/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/es6-promise.js +5 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/et-ee/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/eu-es/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-15.01.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-15.02.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-15.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-mac-16.00-core.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-mac-16.00.js +25 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-web-16.00-core.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-web-16.00.js +25 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-win32-16.00.js +19 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-win32-16.01-core.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-win32-16.01.js +25 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excel-winrt-16.00.js +25 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excelios-15.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excelwebapp-15.01.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excelwebapp-15.02.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/excelwebapp-15.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fa-ir/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fi-fi/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fil-ph/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-be/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-ca/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-ch/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-fr/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-lu/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/fr-mc/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ga-ie/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/gl-es/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/gu-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/he-il/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/hi-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/hr-ba/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/hr-hr/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/html2canvas.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/hu-hu/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/hy-am/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/id-id/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/is-is/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/it-ch/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/it-it/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ja-jp/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ka-ge/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/kk-kz/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/km-kh/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/kn-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ko-kr/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/lb-lu/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/lo-la/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/lt-lt/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/lv-lv/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/mk-mk/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ml-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/mn-mn/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/mr-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ms-bn/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ms-my/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/mt-mt/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/nb-no/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ne-np/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/nl-be/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/nl-nl/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/nn-no/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/o15apptofilemappingtable.js +11 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/office-vsdoc.js +28596 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/office.js +84 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/pl-pl/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/pt-br/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/pt-pt/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ro-ro/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ru-ru/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/si-lk/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sk-sk/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sl-si/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sq-al/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sr-cyrl-cs/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sr-cyrl-rs/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sr-latn-cs/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sr-latn-rs/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sv-fi/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sv-se/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/sw-ke/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ta-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/te-in/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/telemetry/oteljs.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/telemetry/oteljs_agave.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/th-th/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/tr-tr/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/uk-ua/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/ur-pk/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/vi-vn/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/webauth/webauth.browserauth.js +77 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/webauth/webauth.implicit.js +35 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/zh-cn/office_strings.js +1 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/zh-hk/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/zh-mo/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/zh-sg/office_strings.js +8 -0
- xlwings_server/static/vendor/@microsoft/office-js/dist/zh-tw/office_strings.js +1 -0
- xlwings_server/static/vendor/axios/dist/axios.min.js +3 -0
- xlwings_server/static/vendor/axios/dist/axios.min.js.map +1 -0
- xlwings_server/static/vendor/bootstrap/LICENSE +21 -0
- xlwings_server/static/vendor/bootstrap/dist/js/bootstrap.bundle.min.js +7 -0
- xlwings_server/static/vendor/bootstrap/dist/js/bootstrap.bundle.min.js.map +1 -0
- xlwings_server/static/vendor/bootstrap-xlwings/dist/bootstrap-xlwings.min.css +12 -0
- xlwings_server/static/vendor/bootstrap-xlwings/dist/bootstrap-xlwings.min.css.map +1 -0
- xlwings_server/static/vendor/htmx-ext-head-support/head-support.js +144 -0
- xlwings_server/static/vendor/htmx-ext-loading-states/loading-states.js +184 -0
- xlwings_server/static/vendor/htmx.org/LICENSE +13 -0
- xlwings_server/static/vendor/htmx.org/dist/htmx.min.js +1 -0
- xlwings_server/static/vendor/socket.io/LICENSE +22 -0
- xlwings_server/static/vendor/socket.io/client-dist/socket.io.min.js +7 -0
- xlwings_server/static/vendor/socket.io/client-dist/socket.io.min.js.map +1 -0
- xlwings_server/templates/_book.html +8 -0
- xlwings_server/templates/alert_base.html +16 -0
- xlwings_server/templates/base.html +117 -0
- xlwings_server/templates/examples/alpine/README.md +26 -0
- xlwings_server/templates/examples/alpine/taskpane.html +47 -0
- xlwings_server/templates/examples/auth/README.md +38 -0
- xlwings_server/templates/examples/auth/protected.html +8 -0
- xlwings_server/templates/examples/auth/public.html +11 -0
- xlwings_server/templates/examples/excel_object_model/README.md +49 -0
- xlwings_server/templates/examples/excel_object_model/add_name_form.html +27 -0
- xlwings_server/templates/examples/hello_world/README.md +9 -0
- xlwings_server/templates/examples/hello_world/taskpane_hello.html +24 -0
- xlwings_server/templates/examples/htmx_form/README.md +44 -0
- xlwings_server/templates/examples/htmx_form/_greeting.html +6 -0
- xlwings_server/templates/examples/htmx_form/taskpane_htmx_form.html +21 -0
- xlwings_server/templates/examples/live_form_validation/README.md +60 -0
- xlwings_server/templates/examples/live_form_validation/add_name_form.html +33 -0
- xlwings_server/templates/examples/multi_app/README.md +34 -0
- xlwings_server/templates/examples/multi_app/taskpane1.html +7 -0
- xlwings_server/templates/examples/multi_app/taskpane2.html +7 -0
- xlwings_server/templates/examples/multi_app/taskpane_loader.html +5 -0
- xlwings_server/templates/examples/navigation/README.md +28 -0
- xlwings_server/templates/examples/navigation/_navigation.html +16 -0
- xlwings_server/templates/examples/navigation/taskpane_one.html +8 -0
- xlwings_server/templates/examples/navigation/taskpane_three.html +8 -0
- xlwings_server/templates/examples/navigation/taskpane_two.html +8 -0
- xlwings_server/templates/examples/pictures/README.md +42 -0
- xlwings_server/templates/examples/pictures/_picture.html +4 -0
- xlwings_server/templates/examples/pictures/taskpane_pictures.html +26 -0
- xlwings_server/templates/manifest.xml +155 -0
- xlwings_server/templates/taskpane.html +1 -0
- xlwings_server/templates/xlwings_alert.html +27 -0
- xlwings_server/templates.py +61 -0
- xlwings_server/utils.py +32 -0
- xlwings_server/wasm/__init__.py +0 -0
- xlwings_server/wasm/config.py +24 -0
- xlwings_server/wasm/main.py +236 -0
- xlwings_server/wasm/requirements.txt +5 -0
- xlwings_server-1.1.0.dist-info/METADATA +61 -0
- xlwings_server-1.1.0.dist-info/RECORD +313 -0
- xlwings_server-1.1.0.dist-info/WHEEL +4 -0
- xlwings_server-1.1.0.dist-info/entry_points.txt +2 -0
- xlwings_server-1.1.0.dist-info/licenses/LICENSE.md +223 -0
xlwings_server/config.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from importlib.metadata import version as get_package_version
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import UUID4, computed_field
|
|
10
|
+
from pydantic_settings import (
|
|
11
|
+
BaseSettings,
|
|
12
|
+
PydanticBaseSettingsSource,
|
|
13
|
+
PyprojectTomlConfigSettingsSource,
|
|
14
|
+
SettingsConfigDict,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Set before xlwings is imported elsewhere in the application
|
|
20
|
+
os.environ["XLWINGS_ON_SERVER"] = "true"
|
|
21
|
+
|
|
22
|
+
# Get project directory from environment (set by CLI)
|
|
23
|
+
# Falls back to current working directory if not set (for backward compatibility)
|
|
24
|
+
PROJECT_DIR = Path(os.getenv("XLWINGS_PROJECT_DIR", Path.cwd()))
|
|
25
|
+
|
|
26
|
+
# Get package directory - must be calculated before any sys.path manipulation
|
|
27
|
+
# Use __file__ which points to the actual config.py location (in site-packages after install)
|
|
28
|
+
PACKAGE_DIR = Path(__file__).parent.resolve()
|
|
29
|
+
|
|
30
|
+
# Setup sys.path for user overrides
|
|
31
|
+
# This is also done in main.py, but we need it here too because config.py
|
|
32
|
+
# can be imported directly (e.g., from custom_functions) before main.py runs
|
|
33
|
+
if str(PROJECT_DIR) not in sys.path:
|
|
34
|
+
sys.path.insert(0, str(PROJECT_DIR))
|
|
35
|
+
|
|
36
|
+
# Settings prefix used for environment variables (required)
|
|
37
|
+
# and pyproject.toml keys (optional)
|
|
38
|
+
ENV_PREFIX = "XLWINGS_"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CaseInsensitivePyprojectTomlSource(PyprojectTomlConfigSettingsSource):
|
|
42
|
+
"""
|
|
43
|
+
PyprojectToml source that handles case-insensitive keys and strips ENV_PREFIX.
|
|
44
|
+
E.g., allows 'environment', 'ENVIRONMENT', 'XLWINGS_ENVIRONMENT', etc. to all work.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __call__(self) -> dict[str, Any]:
|
|
48
|
+
config = super().__call__()
|
|
49
|
+
normalized = {}
|
|
50
|
+
prefix_lower = ENV_PREFIX.lower()
|
|
51
|
+
for k, v in config.items():
|
|
52
|
+
# Convert to lowercase
|
|
53
|
+
key = k.lower()
|
|
54
|
+
# Strip prefix if present (e.g., "xlwings_")
|
|
55
|
+
if key.startswith(prefix_lower):
|
|
56
|
+
key = key[len(prefix_lower) :]
|
|
57
|
+
normalized[key] = v
|
|
58
|
+
return normalized
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Settings(BaseSettings):
|
|
62
|
+
"""
|
|
63
|
+
xlwings Server settings with support for multiple configuration sources.
|
|
64
|
+
|
|
65
|
+
Configuration priority (highest to lowest):
|
|
66
|
+
1. Environment variables (XLWINGS_*)
|
|
67
|
+
2. .env file
|
|
68
|
+
3. pyproject.toml [tool.xlwings_server] section
|
|
69
|
+
4. Default values defined in this class
|
|
70
|
+
|
|
71
|
+
See .env.template for detailed documentation of all settings.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, **values):
|
|
75
|
+
super().__init__(**values)
|
|
76
|
+
if self.public_addin_store is not None:
|
|
77
|
+
warnings.warn(
|
|
78
|
+
"The 'XLWINGS_PUBLIC_ADDIN_STORE' field is deprecated and will be removed in "
|
|
79
|
+
"future versions. Use 'XLWINGS_CDN_OFFICEJS' instead.",
|
|
80
|
+
DeprecationWarning,
|
|
81
|
+
)
|
|
82
|
+
self.cdn_officejs = self.public_addin_store
|
|
83
|
+
|
|
84
|
+
model_config = SettingsConfigDict(
|
|
85
|
+
env_prefix=ENV_PREFIX,
|
|
86
|
+
env_file=os.getenv("DOTENV_PATH", PROJECT_DIR / ".env"),
|
|
87
|
+
extra="ignore",
|
|
88
|
+
pyproject_toml_table_header=("tool", "xlwings_server"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def settings_customise_sources(
|
|
93
|
+
cls,
|
|
94
|
+
settings_cls: type[BaseSettings],
|
|
95
|
+
init_settings: PydanticBaseSettingsSource,
|
|
96
|
+
env_settings: PydanticBaseSettingsSource,
|
|
97
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
98
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
99
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
100
|
+
"""
|
|
101
|
+
Define the sources and their priority order.
|
|
102
|
+
Priority (highest to lowest):
|
|
103
|
+
1. init_settings - arguments passed to Settings()
|
|
104
|
+
2. env_settings - environment variables
|
|
105
|
+
3. dotenv_settings - .env file
|
|
106
|
+
4. pyproject.toml - [tool.xlwings_server] section
|
|
107
|
+
5. file_secret_settings - secret files
|
|
108
|
+
"""
|
|
109
|
+
# CaseInsensitivePyprojectTomlSource looks in PROJECT_DIR for pyproject.toml
|
|
110
|
+
pyproject_path = PROJECT_DIR / "pyproject.toml"
|
|
111
|
+
return (
|
|
112
|
+
init_settings,
|
|
113
|
+
env_settings,
|
|
114
|
+
dotenv_settings,
|
|
115
|
+
CaseInsensitivePyprojectTomlSource(
|
|
116
|
+
settings_cls, pyproject_path if pyproject_path.exists() else None
|
|
117
|
+
),
|
|
118
|
+
file_secret_settings,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
add_security_headers: bool = True
|
|
122
|
+
auth_providers: list[str] | None = []
|
|
123
|
+
auth_required_roles: list[str] | None = []
|
|
124
|
+
auth_entraid_client_id: str | None = None
|
|
125
|
+
auth_entraid_tenant_id: str | None = None
|
|
126
|
+
auth_entraid_multitenant: bool = False
|
|
127
|
+
app_path: str = ""
|
|
128
|
+
base_dir: Path = Path(__file__).resolve().parent
|
|
129
|
+
object_cache_url: str | None = None
|
|
130
|
+
object_cache_expire_at: str | None = "0 12 * * sat"
|
|
131
|
+
object_cache_enable_compression: bool = True
|
|
132
|
+
cors_allow_origins: list[str] = []
|
|
133
|
+
custom_functions_max_retries: int = 3
|
|
134
|
+
custom_functions_retry_codes: list[int] = [500, 502, 504]
|
|
135
|
+
custom_headers: dict[str, str] = {}
|
|
136
|
+
date_format: str | None = None
|
|
137
|
+
taskpane_html: str = "taskpane.html"
|
|
138
|
+
enable_alpinejs_csp: bool = True
|
|
139
|
+
enable_bootstrap: bool = True
|
|
140
|
+
enable_examples: bool = False
|
|
141
|
+
enable_excel_online: bool = True
|
|
142
|
+
enable_hotreload: bool = True
|
|
143
|
+
enable_htmx: bool = True
|
|
144
|
+
enable_socketio: bool = True
|
|
145
|
+
enable_tests: bool = False
|
|
146
|
+
enable_wasm: bool = False
|
|
147
|
+
environment: Literal["dev", "qa", "uat", "staging", "prod"] = "prod"
|
|
148
|
+
functions_namespace: str = "XLWINGS"
|
|
149
|
+
hostname: str | None = None
|
|
150
|
+
is_official_lite_addin: bool | None = False
|
|
151
|
+
cdn_pyodide: bool = True
|
|
152
|
+
cdn_officejs: bool = False
|
|
153
|
+
log_level: str = "INFO"
|
|
154
|
+
# Manifest UUIDs - loaded from pyproject.toml [tool.xlwings_server] or defaults
|
|
155
|
+
# Run 'xlwings-server init' to generate unique UUIDs in pyproject.toml
|
|
156
|
+
manifest_id_dev: UUID4 = "0a856eb1-91ab-4f38-b757-23fbe1f73130"
|
|
157
|
+
manifest_id_qa: UUID4 = "9cda34b1-af68-4dc6-b97c-e63ef6284671"
|
|
158
|
+
manifest_id_uat: UUID4 = "70428e53-8113-421c-8fe2-9b74fcb94ee5"
|
|
159
|
+
manifest_id_staging: UUID4 = "34041f4f-9cb4-4830-afb5-db44b2a70e0e"
|
|
160
|
+
manifest_id_prod: UUID4 = "4f342d85-3a49-41cb-90a5-37b1f2219040"
|
|
161
|
+
project_name: str = "xlwings Server"
|
|
162
|
+
public_addin_store: bool | None = None # Deprecated. Use cdn_officejs instead.
|
|
163
|
+
request_timeout: int | None = 300 # in seconds
|
|
164
|
+
secret_key: str | None = None
|
|
165
|
+
socketio_message_queue_url: str | None = None
|
|
166
|
+
socketio_server_app: bool = False
|
|
167
|
+
static_url_path: str = "/static"
|
|
168
|
+
license_key: str | None = ""
|
|
169
|
+
xlwings_version: str = get_package_version("xlwings")
|
|
170
|
+
|
|
171
|
+
@computed_field
|
|
172
|
+
@property
|
|
173
|
+
def jsconfig(self) -> dict:
|
|
174
|
+
return {
|
|
175
|
+
"appPath": self.app_path,
|
|
176
|
+
"authProviders": self.auth_providers,
|
|
177
|
+
"customFunctionsMaxRetries": self.custom_functions_max_retries,
|
|
178
|
+
"customFunctionsRetryCodes": self.custom_functions_retry_codes,
|
|
179
|
+
"environment": self.environment,
|
|
180
|
+
"isOfficialLiteAddin": self.is_official_lite_addin,
|
|
181
|
+
"onWasm": self.enable_wasm,
|
|
182
|
+
"requestTimeout": self.request_timeout,
|
|
183
|
+
"xlwingsVersion": self.xlwings_version,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@computed_field
|
|
187
|
+
@property
|
|
188
|
+
def project_dir(self) -> Path:
|
|
189
|
+
"""Project directory - location of user's custom files"""
|
|
190
|
+
return PROJECT_DIR
|
|
191
|
+
|
|
192
|
+
@computed_field
|
|
193
|
+
@property
|
|
194
|
+
def package_dir(self) -> Path:
|
|
195
|
+
"""Package directory - location of xlwings-server package files"""
|
|
196
|
+
return PACKAGE_DIR
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Try to import Settings from project directory (user override)
|
|
200
|
+
# Fall back to package Settings if not found
|
|
201
|
+
def _create_settings() -> Settings:
|
|
202
|
+
"""Create settings instance, checking for project-level config override"""
|
|
203
|
+
try:
|
|
204
|
+
config_file = PROJECT_DIR / "config.py"
|
|
205
|
+
if config_file.exists():
|
|
206
|
+
# Import Settings from project config
|
|
207
|
+
import importlib
|
|
208
|
+
|
|
209
|
+
config_module = importlib.import_module("config")
|
|
210
|
+
if hasattr(config_module, "Settings"):
|
|
211
|
+
ProjectSettings = config_module.Settings
|
|
212
|
+
logger.info("Loaded settings from project config.py")
|
|
213
|
+
return ProjectSettings()
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.debug(f"No project config.py found or failed to load: {e}")
|
|
216
|
+
|
|
217
|
+
# No project config or import failed, use package Settings
|
|
218
|
+
return Settings()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
settings = _create_settings()
|
|
222
|
+
|
|
223
|
+
# TODO: refactor once xlwings offers a runtime config
|
|
224
|
+
if settings.license_key and not os.getenv("XLWINGS_LICENSE_KEY"):
|
|
225
|
+
os.environ["XLWINGS_LICENSE_KEY"] = settings.license_key
|
|
226
|
+
|
|
227
|
+
if settings.date_format:
|
|
228
|
+
os.environ["XLWINGS_DATE_FORMAT"] = settings.date_format
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Make sure to import the desired functions under __init__.py, e.g.:
|
|
3
|
+
from .example import *
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from xlwings.server import arg, func, ret
|
|
15
|
+
|
|
16
|
+
from . import settings
|
|
17
|
+
|
|
18
|
+
if not settings.enable_wasm:
|
|
19
|
+
# Try to import custom_scripts from project directory first (CLI/Azure mode)
|
|
20
|
+
# Fall back to package location (tests/package mode)
|
|
21
|
+
try:
|
|
22
|
+
import custom_scripts
|
|
23
|
+
except ModuleNotFoundError:
|
|
24
|
+
import xlwings_server.custom_scripts as custom_scripts
|
|
25
|
+
|
|
26
|
+
from xlwings.constants import ObjectHandleIcons
|
|
27
|
+
from xlwings.ext.sql import _sql
|
|
28
|
+
|
|
29
|
+
from xlwings_server import utils
|
|
30
|
+
from xlwings_server.models import CurrentUser
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# 1) This is the most basic custom function -- it only requires the @func decorator.
|
|
34
|
+
@func
|
|
35
|
+
def hello(name):
|
|
36
|
+
return f"Hello {name}!"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# 2) Returning a pandas DataFrame and function documentation
|
|
40
|
+
# The function's doc string will turn up in the Function Wizard together with the
|
|
41
|
+
# doc strings from the arg decorators. The sample also shows how to suppress the index
|
|
42
|
+
# of a DataFrame via the ret decorator.
|
|
43
|
+
@func
|
|
44
|
+
@arg("rows", doc="The number of rows in the returned array.")
|
|
45
|
+
@arg("cols", doc="The number of columns in the returned array.")
|
|
46
|
+
def standard_normal(rows, cols):
|
|
47
|
+
"""Returns an array of standard normally distributed pseudo random numbers"""
|
|
48
|
+
rng = np.random.default_rng()
|
|
49
|
+
matrix = rng.standard_normal(size=(rows, cols))
|
|
50
|
+
date_rng = pd.date_range(start=dt.datetime(2025, 6, 15), periods=rows, freq="D")
|
|
51
|
+
df = pd.DataFrame(
|
|
52
|
+
matrix, columns=[f"col{i+1}" for i in range(matrix.shape[1])], index=date_rng
|
|
53
|
+
)
|
|
54
|
+
return df
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# 3) Reading a pandas DataFrames
|
|
58
|
+
@func
|
|
59
|
+
@arg("df", pd.DataFrame)
|
|
60
|
+
def correl(df):
|
|
61
|
+
"""Like CORREL, but it works on whole matrices instead of just 2 arrays."""
|
|
62
|
+
return df.corr()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# 4) Type hints: this is the same example as 3), but using type hints instead of
|
|
66
|
+
# decorators. You could also use type hints and decorators together. In this sample, we
|
|
67
|
+
# are storing the Annotated type hint outside of the function, so it is easy to reuse.
|
|
68
|
+
Df = Annotated[pd.DataFrame, {"index": True}]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@func
|
|
72
|
+
def correl2(df: Df):
|
|
73
|
+
"""Like CORREL, but it works on whole matrices instead of just 2 arrays."""
|
|
74
|
+
return df.corr()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if not settings.enable_wasm:
|
|
78
|
+
# 5) Object handles: This returns an object handle to a DataFrame that is generated on
|
|
79
|
+
# the backend. You can change the `text` and `icon` via annotated type hint or via ret
|
|
80
|
+
# decorator.
|
|
81
|
+
@func
|
|
82
|
+
async def get_df() -> object:
|
|
83
|
+
"""Returns an object handle to the Excel cell (for production, this requires
|
|
84
|
+
XLWINGS_OBJECT_CACHE_URL)."""
|
|
85
|
+
return pd.DataFrame(
|
|
86
|
+
{"A": [1, 2, 3, 4, 5], "B": [10, 8, 6, 4, 2], "C": [10, 9, 8, 7, 6]}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 6) Object handles: demonstrating the use of the "icon" and "text" options. Instead
|
|
90
|
+
# of using the ret decorator, you could also use an annotated type hint like so:
|
|
91
|
+
# -> Annotated[object, {"icon": ObjectHandleIcons.table, "text": "healthexp"}]
|
|
92
|
+
@func
|
|
93
|
+
@ret(icon=ObjectHandleIcons.table, text="healthexp")
|
|
94
|
+
async def get_healthexp(
|
|
95
|
+
csv_url="https://raw.githubusercontent.com/mwaskom/seaborn-data/master/healthexp.csv",
|
|
96
|
+
) -> object:
|
|
97
|
+
"""Returns an object handle to the Excel cell (for production, this requires
|
|
98
|
+
XLWINGS_OBJECT_CACHE_URL)."""
|
|
99
|
+
return pd.read_csv(csv_url)
|
|
100
|
+
|
|
101
|
+
# 7) Object handles: turn an Excel range into a DataFrame object handle. Using an Excel
|
|
102
|
+
# table as source makes it easy to work with dynamic source ranges. This sample reuses
|
|
103
|
+
# the Df type hint from above to set index=False.
|
|
104
|
+
@func
|
|
105
|
+
async def to_df(df: Df) -> object:
|
|
106
|
+
return df
|
|
107
|
+
|
|
108
|
+
# 8) Object handles: use a pandas DataFrame query by providing a DataFrame via object
|
|
109
|
+
# handle and the query as string: [NAMESPACE].DF_QUERY(A1, "A > B")
|
|
110
|
+
@func
|
|
111
|
+
async def df_query(df: object, query: str) -> Df:
|
|
112
|
+
return df.query(query)
|
|
113
|
+
|
|
114
|
+
# 9) Object handles: Generic function that turns an object handle into Excel values
|
|
115
|
+
@func
|
|
116
|
+
async def view(obj: object, head=None):
|
|
117
|
+
"""Converts an object handle to cell values. `head` can be TRUE or an integer, which
|
|
118
|
+
represents the number of rows from the top that you want to see. TRUE returns the
|
|
119
|
+
first 5 rows.
|
|
120
|
+
"""
|
|
121
|
+
if head and isinstance(head, bool):
|
|
122
|
+
head = 5
|
|
123
|
+
if isinstance(obj, pd.DataFrame) and head:
|
|
124
|
+
return obj.iloc[:head, :]
|
|
125
|
+
elif isinstance(obj, (list, tuple, np.ndarray)) and head:
|
|
126
|
+
return obj[:head]
|
|
127
|
+
else:
|
|
128
|
+
return obj
|
|
129
|
+
|
|
130
|
+
# 10) Object handles: Clear the object cache manually
|
|
131
|
+
@func
|
|
132
|
+
async def clear_object_cache():
|
|
133
|
+
"""Object handle: Clear the object cache manually"""
|
|
134
|
+
await utils.clear_object_cache()
|
|
135
|
+
return "Object cache cleared"
|
|
136
|
+
|
|
137
|
+
# 11) Streaming functions (the modern version of RTD functions)
|
|
138
|
+
@func
|
|
139
|
+
async def streaming_random(rows, cols):
|
|
140
|
+
"""Streaming function: must be provided as async generator,
|
|
141
|
+
requires XLWINGS_ENABLE_SOCKETIO=true
|
|
142
|
+
"""
|
|
143
|
+
rng = np.random.default_rng()
|
|
144
|
+
while True:
|
|
145
|
+
matrix = rng.standard_normal(size=(rows, cols))
|
|
146
|
+
df = pd.DataFrame(
|
|
147
|
+
matrix, columns=[f"col{i+1}" for i in range(matrix.shape[1])]
|
|
148
|
+
)
|
|
149
|
+
yield df
|
|
150
|
+
await asyncio.sleep(1)
|
|
151
|
+
|
|
152
|
+
# 12) To access the current user object, simply add an argument with the CurrentUser
|
|
153
|
+
# type hint
|
|
154
|
+
@func
|
|
155
|
+
def get_current_user(current_user: CurrentUser):
|
|
156
|
+
return f"{current_user}"
|
|
157
|
+
|
|
158
|
+
# 13) In-Excel SQL: query Excel ranges/tables via SQL (SQLite dialect)
|
|
159
|
+
@func
|
|
160
|
+
@arg("tables", expand="table", ndim=2)
|
|
161
|
+
def sql(query, *tables):
|
|
162
|
+
"""In-Excel SQL
|
|
163
|
+
see: https://docs.xlwings.org/en/latest/extensions.html#in-excel-sql"""
|
|
164
|
+
return _sql(query, *tables)
|
|
165
|
+
|
|
166
|
+
# 14) Custom functions with side effects (experimental)
|
|
167
|
+
@func
|
|
168
|
+
async def hello_with_script(name):
|
|
169
|
+
"""This function triggers a custom script, requires XLWINGS_ENABLE_SOCKETIO=true"""
|
|
170
|
+
await utils.trigger_script(custom_scripts.hello_world, exclude="MySheet")
|
|
171
|
+
return f"Hello {name}!"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Unit tests
|
|
175
|
+
if settings.enable_tests:
|
|
176
|
+
sys.path.append(str(Path(__file__).parent.parent.resolve()))
|
|
177
|
+
from tests.e2e_custom_functions import *
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Make sure to import the desired functions under __init__.py, e.g.:
|
|
3
|
+
from .example import *
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
except ImportError:
|
|
9
|
+
plt = None
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import xlwings as xw
|
|
15
|
+
from xlwings.server import script
|
|
16
|
+
|
|
17
|
+
from . import settings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@script
|
|
21
|
+
def hello_world(book: xw.Book):
|
|
22
|
+
sheet = book.sheets.active
|
|
23
|
+
cell = sheet["A1"]
|
|
24
|
+
if cell.value == "Hello package!":
|
|
25
|
+
cell.value = "Bye package!"
|
|
26
|
+
else:
|
|
27
|
+
cell.value = "Hello package!"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@script
|
|
31
|
+
def show_alert(book: xw.Book):
|
|
32
|
+
# callback is optional and only required if you want a JS function to be called
|
|
33
|
+
# when clicking a button. alertCallback is defined in app/static/js/core/examples.js
|
|
34
|
+
book.app.alert(
|
|
35
|
+
"This is an alert!",
|
|
36
|
+
title="xlwings Server Alert",
|
|
37
|
+
buttons="ok_cancel",
|
|
38
|
+
callback="alertCallback",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@script
|
|
43
|
+
def setup_custom_functions(book: xw.Book):
|
|
44
|
+
prefix = f"{settings.functions_namespace}"
|
|
45
|
+
if settings.environment != "prod":
|
|
46
|
+
prefix += f"_{settings.environment}".upper()
|
|
47
|
+
sheet = book.sheets.add()
|
|
48
|
+
sheet["A3"].value = f'={prefix}.HELLO("xlwings")'
|
|
49
|
+
sheet["A5"].value = f"={prefix}.STANDARD_NORMAL(3, 4)"
|
|
50
|
+
sheet["A10"].value = f"={prefix}.CORREL(A5#)"
|
|
51
|
+
if not settings.enable_wasm:
|
|
52
|
+
sheet["A16"].value = f"={prefix}.TO_DF(A5#)"
|
|
53
|
+
sheet["A18"].value = f"={prefix}.GET_HEALTHEXP()"
|
|
54
|
+
sheet[
|
|
55
|
+
"A20"
|
|
56
|
+
].value = f"""={prefix}.DF_QUERY(A18, "Country == 'Japan' and Year > 2017")"""
|
|
57
|
+
sheet["A25"].value = f"={prefix}.VIEW(A18, 3)"
|
|
58
|
+
sheet["A30"].value = f"={prefix}.STREAMING_RANDOM(3, 4)"
|
|
59
|
+
sheet["A35"].value = f"={prefix}.GET_CURRENT_USER()"
|
|
60
|
+
sheet[
|
|
61
|
+
"A37"
|
|
62
|
+
].value = f'={prefix}.SQL("SELECT Year, Country FROM a WHERE Spending_USD < 4600", A20#)'
|
|
63
|
+
sheet["A40"].value = f'={prefix}.HELLO_WITH_SCRIPT("xlwings")'
|
|
64
|
+
sheet.activate()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@script
|
|
68
|
+
def show_plot(book: xw.Book):
|
|
69
|
+
"""Adopted from
|
|
70
|
+
https://matplotlib.org/stable/plot_types/stats/hexbin.html#sphx-glr-plot-types-stats-hexbin-py
|
|
71
|
+
"""
|
|
72
|
+
if not plt:
|
|
73
|
+
raise xw.XlwingsError("You need to install Matplotlib for this example")
|
|
74
|
+
plt.style.use("_mpl-gallery-nogrid")
|
|
75
|
+
rng = np.random.default_rng()
|
|
76
|
+
x = rng.standard_normal(5000)
|
|
77
|
+
y = 1.2 * x + rng.standard_normal(5000) / 3
|
|
78
|
+
fig, ax = plt.subplots()
|
|
79
|
+
ax.hexbin(x, y, gridsize=20)
|
|
80
|
+
ax.set(xlim=(-2, 2), ylim=(-3, 3))
|
|
81
|
+
book.sheets.active.pictures.add(
|
|
82
|
+
fig, anchor=book.sheets.active["A10"], update=True, name="mplot"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@script
|
|
87
|
+
def show_error(book: xw.Book):
|
|
88
|
+
raise xw.XlwingsError("This would be your error message")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Unit tests
|
|
92
|
+
if settings.enable_tests:
|
|
93
|
+
sys.path.append(str(Path(__file__).parent.parent.resolve()))
|
|
94
|
+
from tests.e2e_custom_scripts import *
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import redis # TODO: use redis.asyncio when converters can be used async
|
|
2
|
+
|
|
3
|
+
from .config import settings
|
|
4
|
+
|
|
5
|
+
# Redis
|
|
6
|
+
redis_pool = None
|
|
7
|
+
if settings.object_cache_url:
|
|
8
|
+
redis_pool = redis.ConnectionPool.from_url(settings.object_cache_url)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_redis_client():
|
|
12
|
+
if redis_pool is None:
|
|
13
|
+
yield None
|
|
14
|
+
else:
|
|
15
|
+
client = redis.Redis.from_pool(redis_pool)
|
|
16
|
+
try:
|
|
17
|
+
yield client
|
|
18
|
+
finally:
|
|
19
|
+
client.close()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Annotated, Union
|
|
5
|
+
|
|
6
|
+
import redis
|
|
7
|
+
import xlwings as xw
|
|
8
|
+
from fastapi import Depends, Form, Header, HTTPException, Request, status
|
|
9
|
+
|
|
10
|
+
from . import models
|
|
11
|
+
from .config import settings
|
|
12
|
+
from .databases import get_redis_client
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Book
|
|
18
|
+
async def parse_book_input(
|
|
19
|
+
request: Request,
|
|
20
|
+
form_data: str | None = Form(None, alias="bookData"),
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""Helper dependency to parse either form data (htmx)
|
|
23
|
+
or body (custom scripts & custom functions) -- couldn't make Body() work"""
|
|
24
|
+
if form_data:
|
|
25
|
+
return json.loads(form_data)
|
|
26
|
+
else:
|
|
27
|
+
body_bytes = await request.body()
|
|
28
|
+
if body_bytes:
|
|
29
|
+
body_str = body_bytes.decode("utf-8")
|
|
30
|
+
return json.loads(body_str)
|
|
31
|
+
raise HTTPException(status_code=400, detail="No book data provided")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def get_book(book_data: dict = Depends(parse_book_input)):
|
|
35
|
+
"""Book dependency that returns the calling book and cleans it up again"""
|
|
36
|
+
book = xw.Book(json=book_data)
|
|
37
|
+
try:
|
|
38
|
+
yield book
|
|
39
|
+
finally:
|
|
40
|
+
book.close()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
Book = Annotated[xw.Book, Depends(get_book)]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Users/Auth
|
|
47
|
+
async def authenticate(
|
|
48
|
+
token_string: str = Header(default="", alias="Authorization"),
|
|
49
|
+
auth_provider: Union[str, None] = Header(default=None),
|
|
50
|
+
):
|
|
51
|
+
if not settings.auth_providers:
|
|
52
|
+
return User(id="n/a", name="Anonymous")
|
|
53
|
+
if len(settings.auth_providers) == 1:
|
|
54
|
+
provider = settings.auth_providers[0]
|
|
55
|
+
elif len(settings.auth_providers) > 1 and not auth_provider:
|
|
56
|
+
raise HTTPException(
|
|
57
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
58
|
+
detail="With multiple auth providers, you need to provide the Auth-Provider header.",
|
|
59
|
+
)
|
|
60
|
+
elif auth_provider not in settings.auth_providers:
|
|
61
|
+
raise HTTPException(
|
|
62
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
63
|
+
detail="Auth-Provider header wasn't found in XLWINGS_AUTH_PROVIDERS setting.",
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
provider = auth_provider
|
|
67
|
+
logger.info(f"Using auth provider {provider}")
|
|
68
|
+
# Validate the provider before import to prevent non-literal-import SAST flagging
|
|
69
|
+
if provider not in settings.auth_providers:
|
|
70
|
+
raise ValueError(f"Unsupported authentication provider: {provider}")
|
|
71
|
+
try:
|
|
72
|
+
# Try to import from project directory first (user override)
|
|
73
|
+
try:
|
|
74
|
+
module = importlib.import_module(f"auth.{provider}")
|
|
75
|
+
except ModuleNotFoundError:
|
|
76
|
+
# Fall back to package location (default implementation)
|
|
77
|
+
module = importlib.import_module(f"xlwings_server.auth.{provider}")
|
|
78
|
+
current_user = await module.validate_token(token_string)
|
|
79
|
+
except (AttributeError, ModuleNotFoundError):
|
|
80
|
+
logger.exception(f"Auth provider '{provider}' implementation missing.")
|
|
81
|
+
raise HTTPException(
|
|
82
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
83
|
+
detail=f"Auth provider '{provider}' implementation missing.",
|
|
84
|
+
)
|
|
85
|
+
return current_user
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def get_user(request: Request, current_user: models.User = Depends(authenticate)):
|
|
89
|
+
# Extract IP address and attach it to the user object
|
|
90
|
+
if "x-forwarded-for" in request.headers:
|
|
91
|
+
ip_address = request.headers["x-forwarded-for"].split(",")[0].strip()
|
|
92
|
+
else:
|
|
93
|
+
ip_address = request.client.host if request.client else None
|
|
94
|
+
current_user.ip_address = ip_address
|
|
95
|
+
|
|
96
|
+
if not settings.auth_providers:
|
|
97
|
+
return current_user
|
|
98
|
+
# RBAC
|
|
99
|
+
has_required_roles = await current_user.has_required_roles(
|
|
100
|
+
settings.auth_required_roles
|
|
101
|
+
)
|
|
102
|
+
if not has_required_roles:
|
|
103
|
+
missing_roles = ", ".join(
|
|
104
|
+
set(settings.auth_required_roles).difference(current_user.roles)
|
|
105
|
+
)
|
|
106
|
+
msg = f"Auth error: Missing roles for {current_user.name}: {missing_roles}"
|
|
107
|
+
logger.warning(msg)
|
|
108
|
+
raise HTTPException(
|
|
109
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
110
|
+
detail=msg,
|
|
111
|
+
)
|
|
112
|
+
# Authorization
|
|
113
|
+
if not await current_user.is_authorized():
|
|
114
|
+
msg = f"Auth error: Not authorized for {current_user.name}"
|
|
115
|
+
logger.warning(msg)
|
|
116
|
+
raise HTTPException(
|
|
117
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
118
|
+
detail=msg,
|
|
119
|
+
)
|
|
120
|
+
return current_user
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
User = Annotated[models.User, Depends(get_user)]
|
|
124
|
+
|
|
125
|
+
# Redis
|
|
126
|
+
RedisClient = Annotated[redis.Redis, Depends(get_redis_client)]
|