Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fzumstein committed Nov 20, 2024
1 parent 210bca5 commit ef33a08
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 7 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/xlwings-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ jobs:
- name: Install dependencies
run: |
pip install -r requirements-dev.txt
- name: Run tests
- name: Run tests with xlwings Server and .env.test
run: pytest -v
- name: Run tests that depend on env.test2
- name: Run tests with xlwings Server that depend on env.test2 (examples disabled)
env:
ENV_FILE: ".env.test2"
run: pytest tests/test_env2.py
- name: Run tests with xlwings Lite
env:
ENV_FILE: ".env.lite"
run: pytest -v
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"files.associations": {
// Requires "Better Jinja" extension [samuelcolvin.jinjahtml]
"*.html": "jinja-html",
"*.env": "shellscript"
},
// Don't load .env files into Terminal as we handle them via config.py
// or docker-compose, respectively. Terminal would always require a restart to apply
Expand Down
2 changes: 1 addition & 1 deletion app/wasm/custom_functions/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# 1) This is the most basic custom function -- it only requires the @func decorator.
@func
def hello(name):
return f"Hello {name} from WASM!"
return f"Hello {name}!"


# 2) Returning a pandas DataFrame and function documentation
Expand Down
121 changes: 121 additions & 0 deletions tests/.env.lite
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Get a free trial key from https://www.xlwings.org/trial
XLWINGS_LICENSE_KEY="noncommercial"

# Use one of "dev", "qa", "uat", "staging", or "prod". "dev" will activate hotreload for
# the task pane and other dev-specific features. This setting also takes care of loading
# the correct ID in the manifest. Except for "prod", the environment name will show up
# in the add-in name (ProjectName [dev]) and custom functions (NAMESPACE_DEV.MYFUNC)
XLWINGS_ENVIRONMENT="qa"

# Secret key will be set by "python run.py init"
XLWINGS_SECRET_KEY=""

# This sets the HTTP response headers recommended by OWASP. Some of the headers have to
# be less restrictive if XLWINGS_ENABLE_EXCEL_ONLINE=true
XLWINGS_ADD_SECURITY_HEADERS=true

# If you mount xlwings Server on a non-root path (e.g., https://my.domain.com/myapp)
# via a reverse proxy such as nginx, you need to set this to: "/myapp". You most likely
# also need to set the XLWINGS_STATIC_URL_PATH="/myapp/static"
XLWINGS_APP_PATH=""

# To authenticate users, provide the Auth providers as list.
# E.g, to enable Entra ID SSO auth: XLWINGS_AUTH_PROVIDERS=["entraid"]. If you want
# to accept multipe authentication methods, you will need the name as
# Auth-Provider header from the client.
XLWINGS_AUTH_PROVIDERS=[]

# Enable Single Sign-On (SSO) via Microsoft Entra ID (previously Azure AD)
XLWINGS_AUTH_ENTRAID_CLIENT_ID=""
XLWINGS_AUTH_ENTRAID_TENANT_ID=""

# RBAC (role-based access control)
# If your auth provider supports roles, you can list the required roles here.
# E.g.: XLWINGS_AUTH_REQUIRED_ROLES=["xlwings.user"]
XLWINGS_AUTH_REQUIRED_ROLES=[]

# Set this to true if you have users from external organizations
XLWINGS_AUTH_ENTRAID_MULTITENANT=false

# A Redis URL for the caching of object handles. Required for production use.
# Example: redis://host:6379/0 or rediss://host:6379/0
XLWINGS_OBJECT_CACHE_URL=

# This setting expects a cron expression that determines when objects that are cached
# via object handles should be purged from the cache. This requires
# XLWINGS_OBJECT_CACHE_URL to be configured. By default, the object cache is purged
# every Saturday at 12:00 PM (UTC).
XLWINGS_OBJECT_CACHE_EXPIRE_AT="0 12 * * sat"

# If true, cached objects (via object handles) will be compressed when stored in Redis.
# This requires XLWINGS_OBJECT_CACHE_URL to be configured.
XLWINGS_OBJECT_CACHE_ENABLE_COMPRESSION=true

# If you use Office Scripts as the client (instead of Office.js or VBA)
# or custom functions in Excel on the web, you need to configure CORS.
# Otherwise you should disable it by setting it to an empty string.
XLWINGS_CORS_ALLOW_ORIGINS=["*"]

# This allows you to override the date format for custom functions globally.
# Example: XLWINGS_DATE_FORMAT="m/d/yyyy"
XLWINGS_DATE_FORMAT=

# This loads Alpine.js (CSP build) for client-side interactions
# see: https://alpinejs.dev/advanced/csp
XLWINGS_ENABLE_ALPINEJS_CSP=true

# This loads Bootstrap with the xlwings theme
XLWINGS_ENABLE_BOOTSTRAP=true

# There are various examples included under app/templates/examples. If you don't need
# them, you can disable them by setting it to false.
XLWINGS_ENABLE_EXAMPLES=true

# Excel on the web requires less strict security headers
XLWINGS_ENABLE_EXCEL_ONLINE=true

# This loads htmx for client-server interaction, see https://htmx.org
XLWINGS_ENABLE_HTMX=true

# If true, it uses xlwings Lite instead of xlwings Server. This will use Python via WASM
# and won't require a Python installation on the backend. This allows you to deploy the
# add-in to a static file server such as GitHub Pages or Cloudflare Pages for free.
XLWINGS_ENABLE_LITE=true

# This loads Socket.io, which is required for streaming custom functions
# and can be used for realtime interaction on the task pane. Note that this must also
# be true to enable hotreloading of the taskpane during development.
XLWINGS_ENABLE_SOCKETIO=true

# This will be prepended to all custom functions, e.g., "XLWINGS.MYFUNC". Note that
# if the environment is not "prod", it will append the environment to the namespace,
# e.g., XLWINGS_DEV to prevent name clashes when you have the same add-in from multiple
# environments installed.
XLWINGS_FUNCTIONS_NAMESPACE="XLWINGS"

# In case xlwings Server doesn't manage to get the URL correct under /manifest, you
# can set the proper hostname here, e.g., mydomain.com (without the https://)
XLWINGS_HOSTNAME=

# Set the log level to "DEBUG" for more details, but this can log sensitive tokens!
XLWINGS_LOG_LEVEL="INFO"

# This will determine the name of the add-in. If the environment is not "prod", the name
# of the environment will be shown like this: Project Name [dev]
XLWINGS_PROJECT_NAME="Test Project"

# If the add-in will be distributed via Microsoft's public add-in store, set this to
# true to load office.js via their CDN
XLWINGS_PUBLIC_ADDIN_STORE=false

# If you run the Socket.IO server in an own process, you need to configure a message
# queue, such as Redis/Valkey. E.g.: redis://host:6379/0
XLWINGS_SOCKETIO_MESSAGE_QUEUE_URL=

# If you run the Socket.IO server in an own process, you need to set this to true for
# ONLY (!) the app that represents the Socket.IO server.
XLWINGS_SOCKETIO_SERVER_APP=false

# The absolute path to the static files. If you set XLWINGS_APP_PATH to "/myapp", you
# likely need to change this to "/myapp/static".
XLWINGS_STATIC_URL_PATH="/static"
9 changes: 5 additions & 4 deletions tests/test_router_xlwings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@ def test_custom_functions_meta():
assert response.status_code == 200
# run via: pytest -s tests/test_router_xlwings.py::test_custom_functions_meta
# print(repr(response.text))
if settings.enable_lite:
expected = '{"allowCustomDataForDataTypeAny":true,"allowErrorForDataTypeAny":true,"functions":[{"description":"Python function \'hello\'","id":"HELLO","name":"HELLO","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"name","dimensionality":"matrix","type":"any"}]},{"description":"Returns an array of standard normally distributed pseudo random numbers","id":"STANDARD_NORMAL","name":"STANDARD_NORMAL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"The number of rows in the returned array.","name":"rows","dimensionality":"matrix","type":"any"},{"description":"The number of columns in the returned array.","name":"cols","dimensionality":"matrix","type":"any"}]}]}'
else:
expected = '{"allowCustomDataForDataTypeAny":true,"allowErrorForDataTypeAny":true,"functions":[{"description":"Object handle: Clear the object cache manually","id":"CLEAR_OBJECT_CACHE","name":"CLEAR_OBJECT_CACHE","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Like CORREL, but it works on whole matrices instead of just 2 arrays.","id":"CORREL","name":"CORREL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Like CORREL, but it works on whole matrices instead of just 2 arrays.","id":"CORREL2","name":"CORREL2","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'df_query\'","id":"DF_QUERY","name":"DF_QUERY","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"query","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'get_current_user\'","id":"GET_CURRENT_USER","name":"GET_CURRENT_USER","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Returns an object handle to the Excel cell (for production, this requires\\n XLWINGS_OBJECT_CACHE_URL).","id":"GET_DF","name":"GET_DF","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Returns an object handle to the Excel cell (for production, this requires\\n XLWINGS_OBJECT_CACHE_URL).","id":"GET_HEALTHEXP","name":"GET_HEALTHEXP","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"csv_url","dimensionality":"matrix","type":"any","optional":true}]},{"description":"Python function \'hello\'","id":"HELLO","name":"HELLO","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"name","dimensionality":"matrix","type":"any"}]},{"description":"This function triggers a custom script, requires XLWINGS_ENABLE_SOCKETIO=true","id":"HELLO_WITH_SCRIPT","name":"HELLO_WITH_SCRIPT","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"name","dimensionality":"matrix","type":"any"}]},{"description":"In-Excel SQL\\n see: https://docs.xlwings.org/en/latest/extensions.html#in-excel-sql","id":"SQL","name":"SQL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"query","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"tables","dimensionality":"matrix","type":"any","repeating":true}]},{"description":"Returns an array of standard normally distributed pseudo random numbers","id":"STANDARD_NORMAL","name":"STANDARD_NORMAL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"The number of rows in the returned array.","name":"rows","dimensionality":"matrix","type":"any"},{"description":"The number of columns in the returned array.","name":"cols","dimensionality":"matrix","type":"any"}]},{"description":"Streaming function: must be provided as async generator,\\n requires XLWINGS_ENABLE_SOCKETIO=true\\n ","id":"STREAMING_RANDOM","name":"STREAMING_RANDOM","options":{"stream":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"rows","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"cols","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'to_df\'","id":"TO_DF","name":"TO_DF","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Converts an object handle to cell values. `head` can be TRUE or an integer, which\\n represents the number of rows from the top that you want to see. TRUE returns the\\n first 5 rows.\\n ","id":"VIEW","name":"VIEW","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"obj","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"head","dimensionality":"matrix","type":"any","optional":true}]}]}'
assert (
# Python 3.13 seems to strip out multiple spaces, even though that's supposed to only be done on docstrings
response.text.replace(" ", "")
== '{"allowCustomDataForDataTypeAny":true,"allowErrorForDataTypeAny":true,"functions":[{"description":"Object handle: Clear the object cache manually","id":"CLEAR_OBJECT_CACHE","name":"CLEAR_OBJECT_CACHE","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Like CORREL, but it works on whole matrices instead of just 2 arrays.","id":"CORREL","name":"CORREL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Like CORREL, but it works on whole matrices instead of just 2 arrays.","id":"CORREL2","name":"CORREL2","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'df_query\'","id":"DF_QUERY","name":"DF_QUERY","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"query","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'get_current_user\'","id":"GET_CURRENT_USER","name":"GET_CURRENT_USER","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Returns an object handle to the Excel cell (for production, this requires\\n XLWINGS_OBJECT_CACHE_URL).","id":"GET_DF","name":"GET_DF","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[]},{"description":"Returns an object handle to the Excel cell (for production, this requires\\n XLWINGS_OBJECT_CACHE_URL).","id":"GET_HEALTHEXP","name":"GET_HEALTHEXP","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"csv_url","dimensionality":"matrix","type":"any","optional":true}]},{"description":"Python function \'hello\'","id":"HELLO","name":"HELLO","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"name","dimensionality":"matrix","type":"any"}]},{"description":"This function triggers a custom script, requires XLWINGS_ENABLE_SOCKETIO=true","id":"HELLO_WITH_SCRIPT","name":"HELLO_WITH_SCRIPT","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"name","dimensionality":"matrix","type":"any"}]},{"description":"In-Excel SQL\\n see: https://docs.xlwings.org/en/latest/extensions.html#in-excel-sql","id":"SQL","name":"SQL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"query","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"tables","dimensionality":"matrix","type":"any","repeating":true}]},{"description":"Returns an array of standard normally distributed pseudo random numbers","id":"STANDARD_NORMAL","name":"STANDARD_NORMAL","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"The number of rows in the returned array.","name":"rows","dimensionality":"matrix","type":"any"},{"description":"The number of columns in the returned array.","name":"cols","dimensionality":"matrix","type":"any"}]},{"description":"Streaming function: must be provided as async generator,\\n requires XLWINGS_ENABLE_SOCKETIO=true\\n ","id":"STREAMING_RANDOM","name":"STREAMING_RANDOM","options":{"stream":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"rows","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"cols","dimensionality":"matrix","type":"any"}]},{"description":"Python function \'to_df\'","id":"TO_DF","name":"TO_DF","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"df","dimensionality":"matrix","type":"any"}]},{"description":"Converts an object handle to cell values. `head` can be TRUE or an integer, which\\n represents the number of rows from the top that you want to see. TRUE returns the\\n first 5 rows.\\n ","id":"VIEW","name":"VIEW","options":{"requiresAddress":true,"requiresParameterAddresses":true},"result":{"dimensionality":"matrix","type":"any"},"parameters":[{"description":"Positional argument 1","name":"obj","dimensionality":"matrix","type":"any"},{"description":"Positional argument 2","name":"head","dimensionality":"matrix","type":"any","optional":true}]}]}'.replace(
" ", ""
)
response.text.replace(" ", "") == expected.replace(" ", "")
)


Expand Down

0 comments on commit ef33a08

Please sign in to comment.