diff --git a/.env.template b/.env.template index 89355da0..f1b2401d 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,5 @@ # Get a free trial key from https://www.xlwings.org/trial -XLWINGS_LICENSE_KEY="your-license-key" +XLWINGS_LICENSE_KEY="your_license_key" # 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c315ddf..7efacc66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: rev: v3.1.0 hooks: - id: prettier - types_or: [html, javascript, css] + types_or: [html, javascript, css, markdown] args: [--write] additional_dependencies: - prettier@3.1.0 diff --git a/LICENSE.md b/LICENSE.md index 1ccadc94..ee0cd67d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,8 +2,8 @@ xlwings Server is source-available and dual-licensed under one of the following licenses: -* PolyForm Noncommercial License 1.0.0 (non-commercial use is free) -* xlwings PRO End User License Agreement (commercial use requires a paid plan) +- PolyForm Noncommercial License 1.0.0 (non-commercial use is free) +- xlwings PRO End User License Agreement (commercial use requires a paid plan) # PolyForm Noncommercial License 1.0.0 @@ -20,7 +20,7 @@ your licenses. The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright -in it for any permitted purpose. However, you may +in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works @@ -29,7 +29,7 @@ License](#changes-and-new-works-license). ## Distribution License The licensor grants you an additional copyright license -to distribute copies of the software. Your license +to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license). @@ -40,7 +40,7 @@ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided -with the software. For example: +with the software. For example: > Required Notice: Copyright Yoyodyne, Inc. (http://example.com) @@ -86,7 +86,7 @@ law. These terms do not limit them. These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from -granting licenses to anyone else. These terms do not imply +granting licenses to anyone else. These terms do not imply any other licenses. ## Patent Defense @@ -104,15 +104,15 @@ violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within -32 days of receiving notice. Otherwise, all your licenses +32 days of receiving notice. Otherwise, all your licenses end immediately. ## No Liability -***As far as the law allows, the software comes as is, without +**_As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use -or nature of the software, under any kind of legal claim.*** +or nature of the software, under any kind of legal claim._** ## Definitions @@ -126,10 +126,10 @@ terms. **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, -or are under common control with that organization. **Control** +or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, -contract, or otherwise. Control can be direct or indirect. +contract, or otherwise. Control can be direct or indirect. **Your licenses** are all the licenses granted to you for the software under these terms. @@ -144,84 +144,80 @@ Last updated: June 4, 2021 This End User License Agreement together with the Order Form (the “Agreement”) is between Zoomer Analytics GmbH, a Lucerne/Switzerland corporation (“Zoomer Analytics”), and the Customer signing this Agreement (“Customer”). This Agreement shall be effective as of the Subscription Term Start Date as defined in the applicable Order Form (the “Effective Date”). Please read this Agreement carefully before using the Software. The parties agree to be bound by the following terms and conditions in connection with the subscription to and use of Zoomer Analytics Software as defined herein. If the Customer does not accept the terms of this Agreement, then the Customer must not Access, install or use the Software. - 1. DEFINITIONS -1.1. Access or Accessing shall mean accessing, installing, using, or viewing the Software or any other proprietary information owned by Zoomer Analytics. -1.2. Content Updates means content used by the Software which is updated from time to time. -1.3. Documentation means the documentation for the Software generally supplied by Zoomer Analytics to assist its customers in their use of the Software, including developer guides, manuals, and the functionality specifications. -1.4. End-User means any person or entity to which Licensee provides a Licensee Product with no right to distribute the Software. -1.5. Licensee means any person or entity to whom the License is granted under these Terms. -1.6. Licensee Product means any proprietary software product owned or otherwise controlled by Licensee, in which the Software has been incorporated, and which Licensee may use for its internal purposes or make available to End-Users. -1.7. Order Form means Zoomer Analytics’ order form or other ordering document signed or referenced by Customer or its authorized reseller which identifies the specific Software ordered, the Product Limitations, and the price agreed upon by the parties. -1.8. Product Limitations means the capacity indicated on the Order Form, including number of Licensees. -1.9. Software shall mean the xlwings PRO software licensed to Licensee under these Terms. Unless otherwise noted, the Software and Documentation are referred to collectively herein as “Software”. -1.10. Subscription Term means the period in which Licensee is authorized to utilize the Software. The Subscription Term shall be listed on the applicable Order Form and shall commence on the Effective Date. - -2. LICENSE - -2.1. Developer License. Under the Developer License Zoomer Analytics grants to Licensee a non-exclusive, non-transferrable, non-sublicensable otherwise than to the Developer, irrevocable subject to compliance with the terms hereof, right to use, test, examine, reproduce, modify, create works based upon (“Derivative Works”), and distribute or otherwise make available, the Software and Derivative Works. The Developer License is a per-seat license meaning that where Licensee enables two or more of its Developers to make use of the rights granted hereby it should purchase the Developer License for each one of them. -2.2. Evaluation License. If Customer’s license is for a trial or evaluation only, then the Subscription Term shall be thirty days, or the trial or evaluation term specified on the Order Form. Customer may not utilize the same software for more than one trial or evaluation term in any twelve-month period, unless otherwise agreed to by Zoomer Analytics. Zoomer Analytics may revoke Customer’s evaluation or trial license at any time and for any reason. Sections 4 (Limited Warranty) and 8.1 (Indemnification) shall not be applicable to any evaluation or trial license. -2.3. Use by Affiliates. Subject to the Product Limitations, Customer may make the Software available to its Affiliates under these terms, provided that Customer is liable for any breach of this Agreement by any of its Affiliates. “Affiliate(s)” means any entity now existing that is directly or indirectly controlled by Customer. For purposes of this definition “control” means the direct possession of a majority of the outstanding voting securities of an entity. -2.4. Delivery and Copies. Delivery shall be deemed to have been made upon Zoomer Analytics providing instructions to download or activate the Software, as applicable. Notwithstanding anything to the contrary herein, Customer may make a reasonable number of copies of the Software for the sole purpose of backing-up and archiving the Software. Each copy of the Software is subject to this Agreement and must contain the same titles, trademarks, and copyright notices as the original. -2.5. Restrictions. Except for the Developer License granted to the Customer in Section 2.1, Zoomer Analytics does not grant to the Customer any other licenses to Software or any other proprietary information owned by Zoomer Analytics. Notwithstanding any other provision of this Agreement, the Customer shall not: -(a) Use Software in any manner to provide service bureau, time-sharing or other computer services to third parties. -(b) Use Software, or allow the transfer, transmission, export, or re-export of Software or portion thereof in violation of any export control laws or regulations administered by any government agency. -(c) Remove, modify or obscure any copyright, trademark, legal notices, or other proprietary notations in Software. -(d) Distribute Software or its modifications as part of any application that can be described as a software competitive with the Software. -(e) Allow Access to, disclose, transfer, or distribute the Software to any party other than to a Licensee or End-User. -(f) Rent, sublicense, lease, or sell the Software, or make any attempt to do so. -(g) Use the Software to perform any illegal, dishonest, or fraudulent act, to damage or injure a third party, or to infringe the intellectual property or privacy rights of any person or entity. -(h) Use the Software in any manner that could be detrimental to Zoomer Analytics. +1.1. Access or Accessing shall mean accessing, installing, using, or viewing the Software or any other proprietary information owned by Zoomer Analytics. +1.2. Content Updates means content used by the Software which is updated from time to time. +1.3. Documentation means the documentation for the Software generally supplied by Zoomer Analytics to assist its customers in their use of the Software, including developer guides, manuals, and the functionality specifications. +1.4. End-User means any person or entity to which Licensee provides a Licensee Product with no right to distribute the Software. +1.5. Licensee means any person or entity to whom the License is granted under these Terms. +1.6. Licensee Product means any proprietary software product owned or otherwise controlled by Licensee, in which the Software has been incorporated, and which Licensee may use for its internal purposes or make available to End-Users. +1.7. Order Form means Zoomer Analytics’ order form or other ordering document signed or referenced by Customer or its authorized reseller which identifies the specific Software ordered, the Product Limitations, and the price agreed upon by the parties. +1.8. Product Limitations means the capacity indicated on the Order Form, including number of Licensees. +1.9. Software shall mean the xlwings PRO software licensed to Licensee under these Terms. Unless otherwise noted, the Software and Documentation are referred to collectively herein as “Software”. +1.10. Subscription Term means the period in which Licensee is authorized to utilize the Software. The Subscription Term shall be listed on the applicable Order Form and shall commence on the Effective Date. + +2. LICENSE + +2.1. Developer License. Under the Developer License Zoomer Analytics grants to Licensee a non-exclusive, non-transferrable, non-sublicensable otherwise than to the Developer, irrevocable subject to compliance with the terms hereof, right to use, test, examine, reproduce, modify, create works based upon (“Derivative Works”), and distribute or otherwise make available, the Software and Derivative Works. The Developer License is a per-seat license meaning that where Licensee enables two or more of its Developers to make use of the rights granted hereby it should purchase the Developer License for each one of them. +2.2. Evaluation License. If Customer’s license is for a trial or evaluation only, then the Subscription Term shall be thirty days, or the trial or evaluation term specified on the Order Form. Customer may not utilize the same software for more than one trial or evaluation term in any twelve-month period, unless otherwise agreed to by Zoomer Analytics. Zoomer Analytics may revoke Customer’s evaluation or trial license at any time and for any reason. Sections 4 (Limited Warranty) and 8.1 (Indemnification) shall not be applicable to any evaluation or trial license. +2.3. Use by Affiliates. Subject to the Product Limitations, Customer may make the Software available to its Affiliates under these terms, provided that Customer is liable for any breach of this Agreement by any of its Affiliates. “Affiliate(s)” means any entity now existing that is directly or indirectly controlled by Customer. For purposes of this definition “control” means the direct possession of a majority of the outstanding voting securities of an entity. +2.4. Delivery and Copies. Delivery shall be deemed to have been made upon Zoomer Analytics providing instructions to download or activate the Software, as applicable. Notwithstanding anything to the contrary herein, Customer may make a reasonable number of copies of the Software for the sole purpose of backing-up and archiving the Software. Each copy of the Software is subject to this Agreement and must contain the same titles, trademarks, and copyright notices as the original. +2.5. Restrictions. Except for the Developer License granted to the Customer in Section 2.1, Zoomer Analytics does not grant to the Customer any other licenses to Software or any other proprietary information owned by Zoomer Analytics. Notwithstanding any other provision of this Agreement, the Customer shall not: +(a) Use Software in any manner to provide service bureau, time-sharing or other computer services to third parties. +(b) Use Software, or allow the transfer, transmission, export, or re-export of Software or portion thereof in violation of any export control laws or regulations administered by any government agency. +(c) Remove, modify or obscure any copyright, trademark, legal notices, or other proprietary notations in Software. +(d) Distribute Software or its modifications as part of any application that can be described as a software competitive with the Software. +(e) Allow Access to, disclose, transfer, or distribute the Software to any party other than to a Licensee or End-User. +(f) Rent, sublicense, lease, or sell the Software, or make any attempt to do so. +(g) Use the Software to perform any illegal, dishonest, or fraudulent act, to damage or injure a third party, or to infringe the intellectual property or privacy rights of any person or entity. +(h) Use the Software in any manner that could be detrimental to Zoomer Analytics. The Customer represents and warrants to Zoomer Analytics that the Customer will comply at all times with the terms of this Agreement and all applicable laws and regulations in using the Software. If the Licensee or any End-Users uses the Software in an unlawful manner, for unlawful purposes or in any way that does not comply with this Agreement or all applicable laws and regulations, then the Software License may be revoked by Zoomer Analytics and, in such event, the Licensee and its End-Users will immediately cease any use of the Software. -2.6. Ownership of Software. Zoomer Analytics retains all right, title, and interest in and to the Documentation, Software, Content Updates and in all copies, modifications and derivative works thereto including, without limitation, all rights to patent, copyright, trade secret, trademark, and other proprietary or intellectual property rights. -2.7. Customer Systems. Customer represents and warrants that it has the appropriate authorizations from the owner of the hardware and other involved systems on which it deploys the Software. -2.8. Open Source. The open source libraries in the Software are included in compliance with pertaining licenses and subject to any such disclaimers and limitations on liability as set forth in the open source library licenses. Zoomer Analytics expressly rejects and disclaims any and all liability for their content, consequences, or fitness for Licensee's purposes. The full list of open source libraries included in the Software can be found at https://docs.xlwings.org/en/stable/license.html#open-source-licenses. +2.6. Ownership of Software. Zoomer Analytics retains all right, title, and interest in and to the Documentation, Software, Content Updates and in all copies, modifications and derivative works thereto including, without limitation, all rights to patent, copyright, trade secret, trademark, and other proprietary or intellectual property rights. +2.7. Customer Systems. Customer represents and warrants that it has the appropriate authorizations from the owner of the hardware and other involved systems on which it deploys the Software. +2.8. Open Source. The open source libraries in the Software are included in compliance with pertaining licenses and subject to any such disclaimers and limitations on liability as set forth in the open source library licenses. Zoomer Analytics expressly rejects and disclaims any and all liability for their content, consequences, or fitness for Licensee's purposes. The full list of open source libraries included in the Software can be found at https://docs.xlwings.org/en/stable/license.html#open-source-licenses. -3. FEES AND PAYMENT TERMS +3. FEES AND PAYMENT TERMS Customer agrees to pay the fees, charges, and other amounts in accordance with the Order Form from the date of invoice. All fees are nonrefundable, unless otherwise stated herein. If Customer is purchasing the Software through a Zoomer Analytics authorized reseller, then the fees shall be as set forth between Customer and reseller and the applicable fees shall be paid directly to the reseller. Customer shall be responsible for all taxes levied on any transaction under this Agreement, including, without limitation, all federal, state, and local sales taxes, levies and assessments, and local withholding taxes in Customer’s jurisdiction, if any, excluding, however, any taxes based on Zoomer Analytics' income. In the event Customer is required to withhold taxes from its payment or withholding taxes are subsequently required to be paid to a local taxing jurisdiction, Customer is obligated to pay such tax, and Zoomer Analytics as applicable, will receive the full payment, net of any such taxes, as agreed in the applicable Order Form and Customer shall provide to Zoomer Analytics written evidence that such withholding tax payment was made. Note that for Swiss-based Customers, VAT will be added to the total amount of fees and included in the invoice. +4. LIMITED WARRANTY +4.1. Software Warranty. Zoomer Analytics warrants that during the Subscription Term the Software will conform, in all material respects, with the applicable Documentation. Zoomer Analytics makes no warranty regarding third party features or services. For a breach of the above warranty, Zoomer Analytics will, at no additional cost to Customer, use commercially reasonable efforts to provide remedial services necessary to enable the Software to conform to the warranty. If Zoomer Analytics is unable to restore such functionality, Customer shall be entitled to terminate the applicable Order Form and receive a pro rata refund of the fees paid. Customer will provide Zoomer Analytics with a reasonable opportunity to remedy any breach and reasonable assistance in remedying any defects. The remedies set out in this subsection are Customer’s sole remedies for breach of the above warranty. +4.2. Disclaimer. ZOOMER ANALYTICS DOES NOT REPRESENT THAT THE SOFTWARE WILL BE UNINTERRUPTED, ERROR-FREE, OR WILL MEET CUSTOMER’S REQUIREMENTS. EXCEPT FOR THE WARRANTY ABOVE, ZOOMER ANALYTICS MAKES NO OTHER WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THOSE OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. -4. LIMITED WARRANTY - -4.1. Software Warranty. Zoomer Analytics warrants that during the Subscription Term the Software will conform, in all material respects, with the applicable Documentation. Zoomer Analytics makes no warranty regarding third party features or services. For a breach of the above warranty, Zoomer Analytics will, at no additional cost to Customer, use commercially reasonable efforts to provide remedial services necessary to enable the Software to conform to the warranty. If Zoomer Analytics is unable to restore such functionality, Customer shall be entitled to terminate the applicable Order Form and receive a pro rata refund of the fees paid. Customer will provide Zoomer Analytics with a reasonable opportunity to remedy any breach and reasonable assistance in remedying any defects. The remedies set out in this subsection are Customer’s sole remedies for breach of the above warranty. -4.2. Disclaimer. ZOOMER ANALYTICS DOES NOT REPRESENT THAT THE SOFTWARE WILL BE UNINTERRUPTED, ERROR-FREE, OR WILL MEET CUSTOMER’S REQUIREMENTS. EXCEPT FOR THE WARRANTY ABOVE, ZOOMER ANALYTICS MAKES NO OTHER WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THOSE OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. - -5. LIMITATION OF LIABILITY - -5.1. Limitation on Indirect Liability. NEITHER PARTY WILL BE LIABLE UNDER THIS AGREEMENT FOR LOST REVENUES OR INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, EVEN IF THE PARTY KNEW OR SHOULD HAVE KNOWN THAT SUCH DAMAGES WERE POSSIBLE. -5.2. Limitation on Amount of Liability. NEITHER PARTY MAY BE HELD LIABLE UNDER THIS AGREEMENT FOR MORE THAN THE AMOUNT PAID OR PAYABLE BY CUSTOMER TO ZOOMER ANALYTICS HEREUNDER DURING THE TWELVE MONTHS PRIOR TO THE EVENT GIVING RISE TO LIABILITY. -5.3. Exceptions to Limitations. The limitations of liability in Section 5.2 apply to the fullest extent permitted by applicable law, except that there is no limitation on loss, claims, or damages directly arising out of violations of: (i) a party's intellectual property rights by the other party; (ii) use of the Software in excess of the Product Limitations; or (iii) a party’s indemnification obligations. +5. LIMITATION OF LIABILITY +5.1. Limitation on Indirect Liability. NEITHER PARTY WILL BE LIABLE UNDER THIS AGREEMENT FOR LOST REVENUES OR INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, EVEN IF THE PARTY KNEW OR SHOULD HAVE KNOWN THAT SUCH DAMAGES WERE POSSIBLE. +5.2. Limitation on Amount of Liability. NEITHER PARTY MAY BE HELD LIABLE UNDER THIS AGREEMENT FOR MORE THAN THE AMOUNT PAID OR PAYABLE BY CUSTOMER TO ZOOMER ANALYTICS HEREUNDER DURING THE TWELVE MONTHS PRIOR TO THE EVENT GIVING RISE TO LIABILITY. +5.3. Exceptions to Limitations. The limitations of liability in Section 5.2 apply to the fullest extent permitted by applicable law, except that there is no limitation on loss, claims, or damages directly arising out of violations of: (i) a party's intellectual property rights by the other party; (ii) use of the Software in excess of the Product Limitations; or (iii) a party’s indemnification obligations. -6. CONFIDENTIALITY +6. CONFIDENTIALITY -6.1. Confidential Information. During the term of this Agreement, each party will regard any information provided to it by the other party and designated in writing as proprietary or confidential as confidential (“Confidential Information”). Confidential Information shall also include information which a reasonable person familiar with the disclosing party’s business and the industry in which it operates would know is of a confidential or proprietary nature. A party will not disclose the other party’s Confidential Information to any third party without the prior written consent of the other party, nor make use of any of the other party’s Confidential Information except in its performance under this Agreement. Each party accepts responsibility for the actions of its agents or employees and shall protect the other party’s Confidential Information in the same manner as it protects its own Confidential Information of a similar nature, but in no event with less than reasonable care. The parties expressly agree that the terms and pricing of this Agreement are Confidential Information. A receiving party shall promptly notify the disclosing party upon becoming aware of a breach or threatened breach hereunder and shall cooperate with any reasonable request of the disclosing party in enforcing its rights. -6.2. Exclusions. Information will not be deemed Confidential Information if such information: (i) is known prior to receipt from the disclosing party, without any obligation of confidentiality; (ii) becomes known to the receiving party directly or indirectly from a source other than one having an obligation of confidentiality to the disclosing party; (iii) becomes publicly known or otherwise publicly available, except through a breach of this Agreement; or (iv) is independently developed by the receiving party without use of the disclosing party’s Confidential Information. The receiving party may disclose Confidential Information pursuant to the requirements of applicable law, stock exchange rule, legal process or government regulation, provided that, unless prohibited from doing so by law enforcement or court order, the receiving party gives the disclosing party reasonable prior written notice, and such disclosure is otherwise limited to the required disclosure. +6.1. Confidential Information. During the term of this Agreement, each party will regard any information provided to it by the other party and designated in writing as proprietary or confidential as confidential (“Confidential Information”). Confidential Information shall also include information which a reasonable person familiar with the disclosing party’s business and the industry in which it operates would know is of a confidential or proprietary nature. A party will not disclose the other party’s Confidential Information to any third party without the prior written consent of the other party, nor make use of any of the other party’s Confidential Information except in its performance under this Agreement. Each party accepts responsibility for the actions of its agents or employees and shall protect the other party’s Confidential Information in the same manner as it protects its own Confidential Information of a similar nature, but in no event with less than reasonable care. The parties expressly agree that the terms and pricing of this Agreement are Confidential Information. A receiving party shall promptly notify the disclosing party upon becoming aware of a breach or threatened breach hereunder and shall cooperate with any reasonable request of the disclosing party in enforcing its rights. +6.2. Exclusions. Information will not be deemed Confidential Information if such information: (i) is known prior to receipt from the disclosing party, without any obligation of confidentiality; (ii) becomes known to the receiving party directly or indirectly from a source other than one having an obligation of confidentiality to the disclosing party; (iii) becomes publicly known or otherwise publicly available, except through a breach of this Agreement; or (iv) is independently developed by the receiving party without use of the disclosing party’s Confidential Information. The receiving party may disclose Confidential Information pursuant to the requirements of applicable law, stock exchange rule, legal process or government regulation, provided that, unless prohibited from doing so by law enforcement or court order, the receiving party gives the disclosing party reasonable prior written notice, and such disclosure is otherwise limited to the required disclosure. -7. TERM & TERMINATION +7. TERM & TERMINATION The Subscription Term will not automatically renew. Instead, Zoomer Analytics will reach out approximately 30 days before the end of the Subscription Term to see if Customer wants to renew. In case of a renewal, a new Agreement will be extended to the Customer. This Agreement or an Order Form may be terminated: (i) by either party if the other party is adjudicated as bankrupt, or if a petition in bankruptcy is filed against the other party and such petition is not discharged within sixty days of such filing; or (ii) by either party if the other party materially breaches this Agreement or the Order Form and fails to cure such breach to such party’s reasonable satisfaction within thirty days following receipt of written notice thereof. Customer’s license to use the Software shall also terminate upon the expiration of the applicable Subscription Term. Upon any termination of this Agreement or an Order Form by Zoomer Analytics, all applicable licenses are revoked and Customer shall immediately cease use of the applicable Software. Termination of this Agreement or a license granted hereunder shall not relieve Customer of its obligation to pay all fees that have accrued, have been paid, or have become payable by Customer hereunder. All provisions of this Agreement which by their nature are intended to survive the termination of this Agreement shall survive such termination. -8. INDEMNIFICATION +8. INDEMNIFICATION -8.1. By Zoomer Analytics. Zoomer Analytics will indemnify, defend, and hold harmless Customer from and against all liabilities, damages, and costs (including settlement costs and reasonable attorneys' fees) arising out of a third-party claim that the Software infringes or misappropriates any intellectual property right of such third party. Notwithstanding the foregoing, in no event shall Zoomer Analytics have any obligations or liability under this Section arising from: (i) use of any Software in a manner not anticipated by this Agreement or in combination with materials not furnished by Zoomer Analytics; or (ii) any content, information or data provided by Customer or other third parties. If the Software is or is likely to become subject to a claim of infringement or misappropriation, then Zoomer Analytics will, at its sole option and expense, either: (i) obtain for the Customer the right to continue using the Software; (ii) replace or modify the Software to be non-infringing and substantially equivalent to the infringing Software; or (iii) if options (i) and (ii) above cannot be accomplished despite the reasonable efforts of Zoomer Analytics, then Zoomer Analytics may terminate Customer’s rights to use the infringing Software and will refund pro-rata any prepaid fees for the infringing portion of the Software. THE RIGHTS GRANTED TO CUSTOMER UNDER THIS SECTION 8.1 SHALL BE CUSTOMER’S SOLE AND EXCLUSIVE REMEDY FOR ANY ALLEGED INFRINGEMENT BY THE SOFTWARE OF ANY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHT. -8.2. By Customer. Customer will indemnify, defend, and hold harmless Zoomer Analytics from and against all liabilities, damages, and costs (including settlement costs and reasonable attorneys' fees) arising out of a third party claim regarding Customer's: (i) use of the Software in violation of applicable law; or (ii) breach of the representation and warranty made in Section 2.7 of this Agreement. +8.1. By Zoomer Analytics. Zoomer Analytics will indemnify, defend, and hold harmless Customer from and against all liabilities, damages, and costs (including settlement costs and reasonable attorneys' fees) arising out of a third-party claim that the Software infringes or misappropriates any intellectual property right of such third party. Notwithstanding the foregoing, in no event shall Zoomer Analytics have any obligations or liability under this Section arising from: (i) use of any Software in a manner not anticipated by this Agreement or in combination with materials not furnished by Zoomer Analytics; or (ii) any content, information or data provided by Customer or other third parties. If the Software is or is likely to become subject to a claim of infringement or misappropriation, then Zoomer Analytics will, at its sole option and expense, either: (i) obtain for the Customer the right to continue using the Software; (ii) replace or modify the Software to be non-infringing and substantially equivalent to the infringing Software; or (iii) if options (i) and (ii) above cannot be accomplished despite the reasonable efforts of Zoomer Analytics, then Zoomer Analytics may terminate Customer’s rights to use the infringing Software and will refund pro-rata any prepaid fees for the infringing portion of the Software. THE RIGHTS GRANTED TO CUSTOMER UNDER THIS SECTION 8.1 SHALL BE CUSTOMER’S SOLE AND EXCLUSIVE REMEDY FOR ANY ALLEGED INFRINGEMENT BY THE SOFTWARE OF ANY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHT. +8.2. By Customer. Customer will indemnify, defend, and hold harmless Zoomer Analytics from and against all liabilities, damages, and costs (including settlement costs and reasonable attorneys' fees) arising out of a third party claim regarding Customer's: (i) use of the Software in violation of applicable law; or (ii) breach of the representation and warranty made in Section 2.7 of this Agreement. -9. TECHNICAL SUPPORT +9. TECHNICAL SUPPORT -9.1. Support and Maintenance Services. The support and maintenance shall be set forth on the applicable Order Form. +9.1. Support and Maintenance Services. The support and maintenance shall be set forth on the applicable Order Form. -10. GENERAL PROVISIONS +10. GENERAL PROVISIONS -10.1. Miscellaneous. (a) This Agreement shall be construed in accordance with and governed for all purposes by the laws of Lucerne/Switzerland for all questions and controversies arising out of this Agreement and waives all objections to venue and personal jurisdiction in these forums for such disputes; (b) this Agreement, along with the accompanying Order Form(s) constitute the entire agreement and understanding of the parties hereto with respect to the subject matter hereof and supersedes all prior agreements and undertakings, both written and oral; (c) this Agreement and each Order Form may not be modified except by a writing signed by each of the parties; (d) in case any one or more of the provisions contained in this Agreement shall for any reason be held to be invalid, illegal, or unenforceable in any respect, such invalidity, illegality, or unenforceability shall not affect any other provisions of this Agreement, but rather this Agreement shall be construed as if such invalid, illegal, or other unenforceable provision had never been contained herein; (e) Customer shall not assign its rights or obligations hereunder without Zoomer Analytics' advance written consent; (f) subject to the foregoing subsection (e), this Agreement shall be binding upon and shall inure to the benefit of the parties hereto and their successors and permitted assigns; (g) no waiver of any right or remedy hereunder with respect to any occurrence or event on one occasion shall be deemed a waiver of such right or remedy with respect to such occurrence or event on any other occasion; (h) nothing in this Agreement, express or implied, is intended to or shall confer upon any other person any right, benefit, or remedy of any nature whatsoever under or by reason of this Agreement, including but not limited to any of Customer’s own clients, customers, or employees; (i) the headings to the sections of this Agreement are for ease of reference only and shall not affect the interpretation or construction of this Agreement; and (j) in the event of a conflict between the terms of this Agreement and the terms of an Order Form, the terms in the Order Form shall take precedence. -10.2. Data Privacy. Customer represents and warrants that Customer has obtained all necessary rights to permit Zoomer Analytics to process Customer Data from and about Customer. -10.3. Relationship of the Parties. Zoomer Analytics and Customer are independent contractors, and nothing in this Agreement shall be construed as making them partners or creating the relationships of principal and agent between them, for any purpose whatsoever. Neither party shall make any contracts, warranties or representations or assume or create any obligations, express or implied, in the other party’s name or on its behalf. -10.4. Force Majeure. Neither party will be liable for inadequate performance to the extent caused by a condition (for example, natural disaster, act of war or terrorism, riot, labor condition, or internet disturbance) that was beyond the party's reasonable control. -10.5. No Reliance. Customer represents that it has not relied on the availability of any future version of the Software or any future product or service in executing this Agreement or purchasing any Software hereunder. -10.6. Publicity. Customer acknowledges that Zoomer Analytics may use Customer’s name and logo for the purpose of identifying Customer as a customer of Zoomer Analytics products and/or services. Zoomer Analytics will cease using the customer’s name and logo upon written request. +10.1. Miscellaneous. (a) This Agreement shall be construed in accordance with and governed for all purposes by the laws of Lucerne/Switzerland for all questions and controversies arising out of this Agreement and waives all objections to venue and personal jurisdiction in these forums for such disputes; (b) this Agreement, along with the accompanying Order Form(s) constitute the entire agreement and understanding of the parties hereto with respect to the subject matter hereof and supersedes all prior agreements and undertakings, both written and oral; (c) this Agreement and each Order Form may not be modified except by a writing signed by each of the parties; (d) in case any one or more of the provisions contained in this Agreement shall for any reason be held to be invalid, illegal, or unenforceable in any respect, such invalidity, illegality, or unenforceability shall not affect any other provisions of this Agreement, but rather this Agreement shall be construed as if such invalid, illegal, or other unenforceable provision had never been contained herein; (e) Customer shall not assign its rights or obligations hereunder without Zoomer Analytics' advance written consent; (f) subject to the foregoing subsection (e), this Agreement shall be binding upon and shall inure to the benefit of the parties hereto and their successors and permitted assigns; (g) no waiver of any right or remedy hereunder with respect to any occurrence or event on one occasion shall be deemed a waiver of such right or remedy with respect to such occurrence or event on any other occasion; (h) nothing in this Agreement, express or implied, is intended to or shall confer upon any other person any right, benefit, or remedy of any nature whatsoever under or by reason of this Agreement, including but not limited to any of Customer’s own clients, customers, or employees; (i) the headings to the sections of this Agreement are for ease of reference only and shall not affect the interpretation or construction of this Agreement; and (j) in the event of a conflict between the terms of this Agreement and the terms of an Order Form, the terms in the Order Form shall take precedence. +10.2. Data Privacy. Customer represents and warrants that Customer has obtained all necessary rights to permit Zoomer Analytics to process Customer Data from and about Customer. +10.3. Relationship of the Parties. Zoomer Analytics and Customer are independent contractors, and nothing in this Agreement shall be construed as making them partners or creating the relationships of principal and agent between them, for any purpose whatsoever. Neither party shall make any contracts, warranties or representations or assume or create any obligations, express or implied, in the other party’s name or on its behalf. +10.4. Force Majeure. Neither party will be liable for inadequate performance to the extent caused by a condition (for example, natural disaster, act of war or terrorism, riot, labor condition, or internet disturbance) that was beyond the party's reasonable control. +10.5. No Reliance. Customer represents that it has not relied on the availability of any future version of the Software or any future product or service in executing this Agreement or purchasing any Software hereunder. +10.6. Publicity. Customer acknowledges that Zoomer Analytics may use Customer’s name and logo for the purpose of identifying Customer as a customer of Zoomer Analytics products and/or services. Zoomer Analytics will cease using the customer’s name and logo upon written request. diff --git a/app/templates/examples/htmx_form/README.md b/app/templates/examples/htmx_form/README.md index f6e90986..b507b63d 100644 --- a/app/templates/examples/htmx_form/README.md +++ b/app/templates/examples/htmx_form/README.md @@ -3,10 +3,10 @@ This example uses a Bootstrap form, adopted from: https://getbootstrap.com/docs/5.3/forms/overview/#overview -* It sends the content of the input field (requires a "name" tag) to the backend using [htmx](https://htmx.org/) -* On the backend, it calls a custom function and -* returns the result via the `_greeting.html` template. The leading underscore means that it is a partial HTML snippet, not a full page. -* Back on the frontend, htmx takes care of displaying that HTML snippet in the `#result` div via `hx-target` tag. +- It sends the content of the input field (requires a "name" tag) to the backend using [htmx](https://htmx.org/) +- On the backend, it calls a custom function and +- returns the result via the `_greeting.html` template. The leading underscore means that it is a partial HTML snippet, not a full page. +- Back on the frontend, htmx takes care of displaying that HTML snippet in the `#result` div via `hx-target` tag. To try it out, replace `app/routers/taskpane.py` with the following code: diff --git a/app/templates/examples/navigation/README.md b/app/templates/examples/navigation/README.md index 73d8cee5..1b5f9be7 100644 --- a/app/templates/examples/navigation/README.md +++ b/app/templates/examples/navigation/README.md @@ -2,7 +2,6 @@ This sample uses a Bootstrap nav (see https://getbootstrap.com/docs/5.3/components/navs-tabs/#underline) to navigate between 3 different pages, but using only a single taskpane. It makes use of the htmx tag `hx-boost`, which converts traditional anchor tags into partial page loads. - To try it out, replace `app/routers/taskpane.py` with the following code: ```python diff --git a/docs/api_coverage.md b/docs/api_coverage.md deleted file mode 100644 index 5d00b8d1..00000000 --- a/docs/api_coverage.md +++ /dev/null @@ -1,142 +0,0 @@ -# API coverage - - -At the moment, xlwings Server doesn't cover yet 100% of the xlwings API. The following attributes are missing at the moment. If you need them, please reach out so we can prioritize their implementation: - -```text -xlwings.App - - - cut_copy_mode - - quit() - - display_alerts - - startup_path - - calculate() - - status_bar - - path - - version - - screen_updating - - interactive - - enable_events - - calculation - -xlwings.Book - - - to_pdf() - - save() - -xlwings.Characters - - - font - - text - -xlwings.Chart - - - set_source_data() - - to_pdf() - - parent - - delete() - - top - - width - - height - - name - - to_png() - - left - - chart_type - -xlwings.Charts - - - add() - -xlwings.Font - - - size - - italic - - color - - name - - bold - -xlwings.Note - - - delete() - - text - -xlwings.PageSetup - - - print_area - -xlwings.Picture - - - top - - left - - lock_aspect_ratio - -xlwings.Range - - - hyperlink - - formula - - font - - width - - formula2 - - characters - - to_png() - - columns - - height - - formula_array - - paste() - - rows - - note - - merge_cells - - row_height - - get_address() - - merge() - - to_pdf() - - autofill() - - top - - wrap_text - - merge_area - - column_width - - copy_picture() - - table - - unmerge() - - current_region - - left - -xlwings.Shape - - - parent - - delete() - - font - - top - - scale_height() - - activate() - - width - - index - - text - - height - - characters - - name - - type - - scale_width() - - left - -xlwings.Sheet - - - page_setup - - used_range - - shapes - - charts - - autofit() - - copy() - - to_html() - - select() - - visible - -xlwings.Table - - - display_name - - show_table_style_last_column - - show_table_style_column_stripes - - insert_row_range - - show_table_style_first_column - - show_table_style_row_stripes -``` diff --git a/docs/authentication.md b/docs/authentication.md index 9c8a9e13..7287377c 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -1 +1,119 @@ # Authentication + +This tutorial covers both authentication ("who is the user?") and authorization ("does the authenticated user have the required rights?"). + +The most comfortable authentication method is the built-in single sign-on (SSO) via Microsoft Entra ID (previously called Azure AD). You can, however, add your own custom authentication and authorization methods. + +```{note} +The authentication described in this document protects the execution of custom scripts and custom functions, but it currently doesn't affect the Office.js task pane. +``` + +## Authorization + +At the core of the authentication system is the `User` model. You can find it under [`app/models/user.py`](https://github.com/xlwings/xlwings-server/blob/main/app/models/user.py). + +To add global authorization, you can implement the `User.is_authorized()` method. For example, to only allow users with the domain `mydomain.com` to run custom scripts and custom functions, you could do: + +```python +async def is_authorized(self): + return self.domain == "mydomain.com" if self.domain else False +``` + +Likewise, to only authorize users with certain roles, you could write: + +```python +async def is_authorized(self): + return await self.has_required_roles(["xlwings.user", "db.writer"]) +``` + +## Current user object + +If you need access to the current user object from a custom script or a custom function, you can use a function parameter with the type hint `CurrentUser`: + +```python +from ..models import CurrentUser + +@func +def get_current_user(current_user: CurrentUser): + return f"The user's domain is {current_user.domain}" +``` + +## Anonymous user + +By default, there is no authentication provider configured (`XLWINGS_AUTH_PROVIDERS=[]`) and everyone will be able to run custom functions and custom scripts. The current user object will be `User(id='n/a', name='Anonymous', email=None, domain=None, roles=[])`. + +## SSO via Microsoft Entra ID + +```{note} +Single sign-on (SSO) is only available for Office.js add-ins. +``` + +### Enable SSO + +1. [Register your add-in as an app on the Microsoft Identity Platform](https://learn.microsoft.com/en-us/office/dev/add-ins/develop/register-sso-add-in-aad-v2) + +2. Provide the following settings, using your actual ClientID and TenantID that you can find on the Entra ID dashboard: + +```ini +XLWINGS_AUTH_PROVIDERS=["entraid"] +XLWINGS_AUTH_ENTRAID_CLIENT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +XLWINGS_AUTH_ENTRAID_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +``` + +To permit users from external organizations, you additionally need to set the following setting to true: + +```ini +XLWINGS_AUTH_ENTRAID_MULTITENANT=true +``` + +3. Go to the `/manifest` endpoint and sideload the updated version of the manifest (restart Excel). +4. Optionally implement an `User.is_authorized()` method, see [](#authorization). + +### Entra ID Roles + +Optionally, you can work with roles. On the [Azure Dashboard](https://portal.azure.com/), go to `Microsoft Entra ID`. On the left side bar under `Manage`, click on `App registrations`. Click on the correct app, then: + +- `App roles` (left sidebar, under `Manage`): + + - Click on `Create app role` + - `Display name`: e.g. `Writer` + - `Allowed member types`: `Users/Groups` + - `Value`: e.g. `Task.Write` + - `Description`: `Writer` + - Checkbox must be active for `Do you want to enable this app role?` + - `Apply` + +=> Repeat this step to create other roles. + +- Go all the way back to `Microsoft Entra ID`, then under `Enterprise applications` (left sidebar under `Manage`): + - Select the previously created application `xlwings` + - Click on `1. Assign users and groups` + - Click on `Add user/group` + - Under `User`, click on `None Selected` and select a user or group. Confirm with `Select`. + - Under `Role`, click on `None Selected` and select `Writer` or `Reader` (if you don't see any role, wait a moment and reload the page). Confirm with `Select`. + - Repeat the last step to give the user both `Reader` and `Writer` roles + +These roles will be available under `User.roles` and can be used in order to implement role-based access control (RBAC), see [](#authorization). + +### Air-gapped servers + +In order to verify the JWT token that Office.js sends to the backend, the backend needs access to the latest version of the Azure JSON Web Key Set (JWKS). + +If your application runs on an air-gapped server and can't download the JWKS directly from the Internet, you can provide your own function under [`app/auth/entraid/jwks.py`](https://github.com/xlwings/xlwings-server/blob/main/app/auth/entraid/jwks.py) to access the JSON file. For example, you could have a cron job that downloads the JSON document once every 24 hours and stores it in a location where the air-gapped server has access. + +The URL to retrieve the JWKS JSON file is: https://login.microsoftonline.com/common/discovery/v2.0/keys + +## Custom authentication + +To use your own authentication method, activate the `custom` authentication provider: + +```ini +XLWINGS_AUTH_PROVIDERS=["custom"] +``` + +To make this work, you need to implement + +- `validate_token` under [`app/auth/custom/__init__.py`](https://github.com/xlwings/xlwings-server/blob/main/app/auth/custom/__init__.py) +- `globalThis.getAuth` under [`app/static/auth.js`](https://github.com/xlwings/xlwings-server/blob/main/app/static/js/auth.js) + +Essentially, you will need to adjust `globalThis.getAuth` so that it returns the token that you will validate with the `validate_token` function on the backend. diff --git a/docs/aws_app_runner.md b/docs/aws_app_runner.md index 50df06fe..859de7ab 100644 --- a/docs/aws_app_runner.md +++ b/docs/aws_app_runner.md @@ -1 +1,20 @@ # AWS App Runner + +## Files + +- `apprunner.yaml` + +## Deployment + +- In this repo, update `apprunner.yaml` with your `XLWINGS_LICENSE_KEY` either as `env` or `secret` +- In the AWS console, on the App Runner dashboard, click on `Create service` +- Repository type: Source code repository +- Source directory: `/` +- Deployment trigger: Automatic +- Configuration file: Use a configuration file +- Service name: e.g. `xlwings-server` +- Click on `Create & deploy` + +## Limitations + +- AWS App Runner functions don't support WebSockets, i.e., streaming functions and `trigger_script` won't work. diff --git a/docs/azure_functions.md b/docs/azure_functions.md index ccd84640..f410f340 100644 --- a/docs/azure_functions.md +++ b/docs/azure_functions.md @@ -1 +1,104 @@ # Azure Functions + +## Prerequisites + +- Follow [](repo_setup.md) or simply clone the [xlwings Server Repo](https://github.com/xlwings/xlwings-server) if you just want to deploy xlwings Server for a quick test. + +Install the following software: + +- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) +- [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local) + +Before you begin, you'll need to login to Azure: + +```text +az login +``` + +```{note} +If you deploy to Azure Functions using a different method, you should be able to adapt the instructions accordingly. +``` + +## Files + +The following files are part of Azure functions setup and may need to be tweaked: + +- `host.json` +- `local.settings.json` +- `function_app.py` +- `.funcignore` + +## Deployment + +In the commands below, we're going to use the following parameters that you should adjust to match your preferences: + +- The function app: `xlwings-server` +- The resource group: `xlwings-server-rg` +- The storage account: `xlwingsserversa` +- Region: `westeurope` + +You may also want to skip some of the steps, e.g., if you already have an existing resource group or storage account to deploy to. + +1. Create a resource group: + + ```text + az group create --name xlwings-server-rg --location westeurope + ``` + +2. Create storage account: + + ```text + az storage account create --name xlwingsserversa --location westeurope --resource-group xlwings-server-rg --sku Standard_LRS + ``` + +3. Create the function app (if possible, use the same Python version locally as the one specified in this command): + + ```text + az functionapp create --resource-group xlwings-server-rg --consumption-plan-location westeurope --runtime python --runtime-version 3.11 --functions-version 4 --name xlwings-server --os-type linux --storage-account xlwingsserversa + ``` + +4. Set the required environment variables. Make sure to provide your own license key at the end of this command (you can get a free trial key [here](https://www.xlwings.org/trial)). You'll also need to adjust the `XLWINGS_ENVIRONMENT` if this is not the `prod` environment: + + ```text + az functionapp config appsettings set --name xlwings-server --resource-group xlwings-server-rg --settings XLWINGS_ENVIRONMENT=prod XLWINGS_ENABLE_SOCKETIO=false XLWINGS_LICENSE_KEY= + ``` + +5. Run the following to enable the worker process to index the functions: + + ```text + az functionapp config appsettings set --name xlwings-server --resource-group xlwings-server-rg --settings AzureWebJobsFeatureFlags=EnableWorkerIndexing + ``` + +6. Deploy the function app (this is also the command to deploy an update): + + ```text + func azure functionapp publish xlwings-server + ``` + + It should terminate with the following message: + + ```text + Remote build succeeded! + [...] Syncing triggers... + Functions in xlwings-server: + http_app_func - [httpTrigger] + Invoke url: https://xlwings-server.azurewebsites.net//{*route} + ``` + +## Logging + +For app logs, in Azure portal go to: +`Function App` > `My Function App`. Then, under `http_app_func`, click on `Invocations and more`. + +## Cleanup + +After running this tutorial you can get rid of all the resources again by running: + +```text +az group delete --name xlwings-server-rg +``` + +## Limitations + +- Azure functions don't support WebSockets, i.e., streaming functions won't work. +- The function is always called `http_app_func`, see https://github.com/Azure-Samples/fastapi-on-azure-functions/issues/31 diff --git a/docs/changelog.md b/docs/changelog.md index 100875a0..be83cef3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 0.5.4 (Sep 11, 2024) + +- Fixed security headers so that Windows will show the icons on the ribbon correctly. +- Made the HTTP port configurable in the default Dockerfile. +- Upgraded dependencies incl. xlwings to 0.32.2. + ## 0.5.3 (Aug 24, 2024) - Avoid merge conflicts in `requirements` files when merging in upstream. diff --git a/docs/client_configuration.md b/docs/client_configuration.md deleted file mode 100644 index cdec6330..00000000 --- a/docs/client_configuration.md +++ /dev/null @@ -1 +0,0 @@ -# Client Configuration diff --git a/docs/conf.py b/docs/conf.py index b8d4853f..135692c5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,6 @@ extensions = [ "myst_parser", - "sphinx.ext.autosectionlabel", # To make easy intra-page links: :ref:`Title` "sphinx_copybutton", "sphinx_design", ] @@ -25,6 +24,7 @@ myst_heading_anchors = 3 myst_enable_extensions = ["colon_fence", "linkify"] +myst_links_external_new_tab = True # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/custom_functions.md b/docs/custom_functions.md index 79509c06..3335c9ba 100644 --- a/docs/custom_functions.md +++ b/docs/custom_functions.md @@ -1 +1,749 @@ # Custom Functions + +This tutorial teaches you everything about custom functions. Note that custom functions are only supported with Office.js add-ins. + +## Basic syntax + +As you can see in the [examples](https://github.com/xlwings/xlwings-server/blob/main/app/custom_functions/examples.py), the simplest custom function only requires the `@func` decorator: + +```python +from xlwings.server import func + +@func +def hello(name): + return f"Hello {name}!" +``` + +```{note} +- The `func` decorator is imported from `xlwings.server` rather than `xlwings`. +- While it's ok to edit the functions in `examples.py` to try things out, you shouldn't commit the changes to Git to prevent future merge conflicts. Rather, create a new Python module as explained in the next section. +``` + +## Adding new custom functions + +Here is how you can write your own custom functions: + +1. Add a Python module under [`app/custom_functions`](https://github.com/xlwings/xlwings-server/blob/main/app/custom_functions), e.g., `myfunctions.py`. +2. Add the following import statement (highlighted line) to [`app/custom_functions/__init__.py`](https://github.com/xlwings/xlwings-server/blob/main/app/custom_functions/__init__.py): + +```{code-block} python +:emphasize-lines: 6 + +from ..config import settings + +if settings.enable_examples: + from .examples import * + +from .myfunctions import * +``` + +```{note} +After adding new functions or editing the function arguments, you will need to restart Excel. This is not required if you are just editing the body of an existing function. +``` + +## pandas DataFrames + +By using the `@arg` and `@ret` decorators, you can apply converters and options to arguments and the return value, respectively. + +For example, to read in the values of a range as pandas DataFrame and return the correlations without writing out the header and the index, you would write: + +```python +import pandas as pd +from xlwings.server import func, arg, ret + +@func +@arg("df", pd.DataFrame, index=False, header=False) +@ret(index=False, header=False) +def correl2(df): + return df.corr() +``` + +For an overview of the available converters and options, have a look at [Converters and Options](https://docs.xlwings.org/en/latest/converters.html). + +## Using type hints instead of decorators + +You can use type hints instead of or in combination with decorators: + +```python +from xlwings.server import func +import pandas as pd + +@func +def myfunction(df: pd.DataFrame) -> pd.DataFrame: + # df is a DataFrame, do something with it + return df +``` + +In this example, the return type (`-> pd.DataFrame`) is optional, as xlwings automatically checks the type of the returned object. + +If you need to provide additional conversion arguments, you can either provide them via an annotated type hint or via a decorator. Note that when you use type hints and decorators together, decorators override type hints for conversion. + +To set `index=False` for both the argument and the return value, you can annotate the type hint like this: + +```python +from typing import Annotated +from xlwings.server import func +import pandas as pd + +@func +def myfunction( + df: Annotated[pd.DataFrame, {"index": False}] +) -> Annotated[pd.DataFrame, {"index": False}]: + # df is a DataFrame, do something with it + return df +``` + +As this might be a little harder to read, you can extract the type definition, which also allows you to reuse it like so: + +```python +from typing import Annotated +from xlwings.server import func +import pandas as pd + +Df = Annotated[pd.DataFrame, {"index": False}] + +@func +def myfunction(df: Df) -> Df: + # df is a DataFrame, do something with it + return df +``` + +Alternatively, you could also combine type hints with decorators: + +```python +from typing import Annotated +from xlwings.server import func, arg, ret +import pandas as pd + +@func +@arg("df", index=False) +@ret(index=False) +def myfunction(df: pd.DataFrame) -> pd.DataFrame: + # df is a DataFrame, do something with it + return df +``` + +## Variable number of arguments (`*args`) + +Varargs are supported. You can also use a converter, which will be applied to all arguments provided by `*args`: + +```python +from xlwings.server import func, arg + +@func +@arg("*args", pd.DataFrame, index=False) +def concat(*args): + return pd.concat(args) +``` + +and the same with type hints: + +```python +from typing import Annotated +from xlwings.server import func + +@func +def concat(*args: Annotated[pd.DataFrame, {"index": False}]): + return pd.concat(args) +``` + +## Doc strings + +To describe your function and its arguments, you can use a function docstring or the `arg` decorator, respectively: + +```python +from xlwings.server import func, arg + +@func +@arg("name", doc='A name such as "World"') +def hello(name): + """This is a classic Hello World example""" + return f"Hello {name}!" +``` + +And again with type hints: + +```python +from typing import Annotated +from xlwings.server import func + +@func +def hello(name: Annotated[str, {"doc": 'A name such as "World"'}]): + """This is a classic Hello World example""" + return f"Hello {name}!" +``` + +These doc strings will appear in Excel's function wizard/formula builder. Note that the name of the arguments will automatically be shown when typing the formula into a cell (intellisense). + +## Date and time + +Depending on whether you're reading from Excel or writing to Excel, there are different tools available to work with date and time. + +### Reading date and time + +In the context of custom functions, xlwings will detect numbers, strings, and booleans but not cells with a date/time format. Hence, you need to use converters. For single datetime arguments do this: + +```python +import datetime as dt +from xlwings.server import func + +@func +@arg("date", dt.datetime) +def isoformat(date): + return date.isoformat() +``` + +And again with type hints: + +```python +import datetime as dt +from xlwings.server import func + +@func +def isoformat(date: dt.datetime): + return date.isoformat() +``` + +Instead of `dt.datetime`, you can also use `dt.date` to get a date object instead. + +If you have multiple values that you need to convert, you can use the `xlwings.to_datetime()` function: + +```python +import datetime as dt +import xlwings as xw +from xlwings.server import func + +@func +def isoformat(dates): + dates = [xw.to_datetime(d) for d in dates] + return [d.isoformat() for d in dates] +``` + +And if you are dealing with pandas DataFrames, you can simply use the `parse_dates` option. It behaves the same as with `pandas.read_csv()`: + +```python +import pandas as pd +from xlwings.server import func, arg + +@func +@arg("df", pd.DataFrame, parse_dates=[0]) +def timeseries_start(df): + return df.index.min() +``` + +and again with type hints: + +```python +from typing import Annotated +import pandas as pd +from xlwings.server import func + +@func +def timeseries_start(df: Annotated[pd.DataFrame, {"parse_dates": [0]}]): + return df.index.min() +``` + +Like `pandas.read_csv()`, you could also provide `parse_dates` with a list of columns names instead of indices. + +### Writing date and time + +When writing datetime object to Excel, xlwings automatically formats the cells as date if your version of Excel supports data types, so no special handling is required: + +```python +import datetime as dt +import xlwings as xw +from xlwings.server import func + +@func +def pytoday(): + return dt.date.today() +``` + +By default, it will format the date according to the content language of your Excel instance, but you can also override this by explicitly providing the `date_format` option: + +```python +import datetime as dt +import xlwings as xw +from xlwings.server import func + +@func +@ret(date_format="yyyy-m-d") +def pytoday(): + return dt.date.today() +``` + +and again with type hints: + +```python +import datetime as dt +import xlwings as xw +from xlwings.server import func + +@func +def pytoday() -> Annotated[dt.date, {"date_format": "yyyy-m-d"}]: + return dt.date.today() +``` + +For the accepted `date_format` string, consult the [official Excel documentation](https://support.microsoft.com/en-us/office/format-numbers-as-dates-or-times-418bd3fe-0577-47c8-8caa-b4d30c528309). + +```{note} +Some older builds of Excel don't support date formatting and will display the date as date serial instead, requiring you format it manually. See also [](#limitations). +``` + +## Namespace + +A namespace groups related custom functions together by prepending the namespace to the function name, separated with a dot. For example, to have NumPy-related functions show up under the numpy namespace, you could do: + +```python +import numpy as np +from xlwings.server import func + +@func(namespace="numpy") +def standard_normal(rows, columns): + rng = np.random.default_rng() + return rng.standard_normal(size=(rows, columns)) +``` + +This function will be shown as `NUMPY.STANDARD_NORMAL` in Excel. + +### Sub-namespace + +You can create sub-namespaces by including a dot like so: + +```python +@func(namespace="numpy.random") +``` + +This function will be shown as `NUMPY.RANDOM.STANDARD_NORMAL` in Excel. + +### Default namespace + +The default namespace is `XLWINGS`, but you can change it via the following setting: + +``` +XLWINGS_FUNCTIONS_NAMESPACE="XLWINGS" +``` + +```{note} +- After changing the setting, you will need to update your `manifest.xml` with the values from the `/manifest` endpoint. +- The `XLWINGS_ENVIRONMENT` is automatically appended to the global function namespace if it is not `"prod"` so if `XLWINGS_ENVIRONMENT="dev"`, your functions will appear under the namespace `XLWINGS_DEV`. +``` + +If you define a namespace as part of the function decorator while also having a default namespace defined, the namespace from the function decorator will define the sub-namespace. + +## Help URL + +You can include a link to an internet page with more information about your function by using the `help_url` option. The function wizard/formula builder will show that link under "More help on this function". + +```python +from xlwings.server import func + +@func(help_url="https://www.xlwings.org") +def hello(name): + return f"Hello {name}!" +``` + +## Array Dimensions + +If you want your function to accept arguments of any dimensions (as single cell or one- or two-dimensional ranges), you may need to use the `ndim` option to make your code work in every case. Likewise, you can return a simple list in a vertical orientation by using the `transpose` option. + +### Dimension of arguments + +Depending on the dimensionality of the function parameters, xlwings either delivers a scalar, a list, or a nested list: + +- Single cells (e.g., `A1`) arrive as scalar, i.e., number, string, or boolean: `1` or `"text"`, or `True` +- A one-dimensional (vertical or horizontal!) range (e.g. `A1:B1` or `A1:A2`) arrives as list: `[1, 2]` +- A two-dimensional range (e.g., `A1:B2`) arrives as nested list: `[[1, 2], [3, 4]]` + +This behavior is not only consistent in itself, it's also in line with how NumPy works and is often what you want: for example, you can directly loop over a vertical 1-dimensional range of cells. + +However, if the argument can be anything from a single cell to a one- or two-dimensional range, you'll want to use the `ndim` option: this allows you to always get the inputs as a one- or two-dimensional list, no matter what the input dimension is: + +```python +from xlwings.server import func, arg + +@func +@arg("x", ndim=2) +def add_one(x): + return [[cell + 1 for cell in row] for row in data] +``` + +and again with type hints: + +```python +from typing import Annotated +from xlwings.server import func + +@func +def add_one(x: Annotated[float, {"ndim": 2}]): + return [[cell + 1 for cell in row] for row in data] +``` + +The above sample would raise an error if you'd leave away the `ndim=2` and use a single cell as argument `x`. + +### Dimension of return value + +If you need to write out a list in vertical orientation, the `transpose` option comes in handy: + +```python +from xlwings.server import func, ret + +@func +@ret(transpose=True) +def vertical_list(): + return [1, 2, 3, 4] +``` + +and again with type hints: + +```python +from typing import Annotated +from xlwings.server import func + +@func +def vertical_list() -> Annotated[list, {"transpose": True}]: + return [1, 2, 3, 4] +``` + +## Error handling and error cells + +Error cells in Excel such as `#VALUE!` are used to display an error from Python. xlwings reads error cells as `None` by default but also allows you to read them as strings. When writing to Excel, you can Excel have a cell formatted as error. Let's get into the details! + +### Error handling + +Whenever there's an error in Python, the cell value will show `#VALUE!`. To understand what's going on, click on the cell with the error, then hover (don't click!) on the exclamation mark that appears: you'll see the error message. + +If you see `Internal Server Error`, you will need to consult the Python server logs. + +```{note} +When you run xlwings Server with `XLWINGS_ENVIRONMENT=prod`, it only shows `xlwings.XlwingsError` in Excel, but during development with `XLWIINGS_ENVIRONMENT=dev`, it shows all errors. +``` + +### Writing NaN values + +`np.nan` and `pd.NA` will be converted to Excel's `#NUM!` error type. + +### Error cells + +#### Reading error cells + +By default, error cells are converted to `None` (scalars and lists) or `np.nan` (NumPy arrays and pandas DataFrames). If you'd like to get them in their string representation, use `err_to_str` option: + +```python +from xlwings.server import func, arg + +@func +@arg("x", err_to_str=True) +def myfunc(x): + ... +``` + +and again with type hints: + +```python +from typing import Annotated, Any +from xlwings.server import func + +@func +def myfunc(x: Annotated[list[list[Any]], {"err_to_str"=True}): + ... +``` + +#### Writing error cells + +To format cells as proper error cells in Excel, simply use their string representation (`#DIV/0!`, `#N/A`, `#NAME?`, `#NULL!`, `#NUM!`, `#REF!`, `#VALUE!`): + +```python +from xlwings.server import func + +@func +def myfunc(x): + return ["#N/A", "#VALUE!"] +``` + +```{note} +Some older builds of Excel don't support proper error types and will display the error as string instead, see also [](#limitations). +``` + +## Dynamic arrays + +If your return value is a one- or two-dimensional array such as a list, NumPy array, or pandas DataFrame, Excel will automatically spill the values into the surrounding cells by using the native dynamic arrays. There are no code changes required: + +Returning a simple list: + +```python +from xlwings.server import func + +@func +def programming_languages(): + return ["Python", "JavaScript"] +``` + +Returning a NumPy array with standard normally distributed random numbers: + +```python +import numpy as np +from xlwings.server import func + +@func +def standard_normal(rows, columns): + rng = np.random.default_rng() + return rng.standard_normal(size=(rows, columns)) +``` + +Returning a pandas DataFrame: + +```python +import pandas as pd +from xlwings.server import func + +@func +def get_dataframe(): + df = pd.DataFrame({"Language": ["Python", "JavaScript"], "Year": [1991, 1995]}) + return df +``` + +## Volatile functions + +Volatile functions are recalculated whenever Excel calculates something, even if none of the function arguments have changed. To mark a function as volatile, use the `volatile` argument in the `func` decorator: + +```python +import datetime as dt +from xlwings.server import func + +@func(volatile=True) +def last_calculated(): + return f"Last calculated: {dt.datetime.now()}" +``` + +## Asynchronous functions + +Custom functions are always asynchronous, meaning that the cell will show `#BUSY!` during calculation, allowing you to continue using Excel: custom functions don't block Excel's user interface. + +## Streaming functions ("RTD functions") + +In the traditional version of Excel, streaming functions were called "RTD functions" or "RealTimeData functions". However, unlike traditional RTD functions, streaming functions don't use a local COM server. Instead, the process runs as a background task on xlwings Server and pushes updates via WebSockets (using Socket.io) to Excel. What's great about streaming functions is that you can connect to your data source in a single place and stream the values to every Excel installation in your entire company. + +To create a streaming function, you simply need to write an asynchronous generator. That is, you need to use `async def` and `yield` instead of `return`, e.g.: + +```python +import asyncio +import numpy as np +import pandas as pd +from xlwings.server import func + +@func +async def streaming_random(rows, cols): + """A streaming function pushing updates of a random DataFrame every second""" + rng = np.random.default_rng() + while True: + matrix = rng.standard_normal(size=(rows, cols)) + df = pd.DataFrame(matrix, columns=[f"col{i+1}" for i in range(matrix.shape[1])]) + yield df + await asyncio.sleep(1) +``` + +As a bit of a more real-world sample, here's how you can transform a REST API into a streaming function to stream the BTC price: + +```python +import asyncio +import httpx +import pandas as pd +from xlwings.server import func, ret + +@func +@ret(date_format="hh:mm:ss", index=False) +async def btc_price(base_currency="USD"): + while True: + async with httpx.AsyncClient() as client: + response = await client.get( + f"https://cex.io/api/ticker/BTC/{base_currency}" + ) + response_data = response.json() + response_data["timestamp"] = pd.to_datetime( + int(response_data["timestamp"]), unit="s" + ) + df = pd.DataFrame(response_data, index=[0]) + df = df[["pair", "timestamp", "bid", "ask"]] + yield df + await asyncio.sleep(1) +``` + +Key to remember is that you're moving in the async world with streaming functions, so you shouldn't use long-running blocking operations. For example, instead of using `requests` to fetch the data, you should use one of the async libraries such as `httpx` or `aiohttp`. + +## Object handles + +Object handles allow you to return Python objects such as a pandas DataFrame to a single cell. Other custom functions can then use the cell with the object handle as a function argument for further manipulation. This functionality is especially helpful if you have huge amounts of data or if the object can't be "translated" into Excel cells. + +```{figure} ./images/object_handles.png + +``` + +To make a custom function return an object, simply specify the `object` type hint for the return value: + +```python +from typing import Annotated +from xlwings.server import func, ret +from xlwings.constants import ObjectHandleIcons + +@func +async def get_mymodel() -> object: + return pd.DataFrame( + {"A": [1, 2, 3, 4, 5], "B": [10, 8, 6, 4, 2], "C": [10, 9, 8, 7, 6]} + ) +``` + +By default, this will display an icon in the cell together with the data type of the object (cell `A1` in the screenshot). By clicking on the icon, you will get some info about that object. You can, however, add valuable information by specifying a different text and/or icon (cell `A3` in the screenshot). You can use an annotated type hint for this or provide the additional arguments via the `ret` decorator: + +```python +@func +@ret(icon=ObjectHandleIcons.table, text="My Model") +async def get_mymodel() -> object: + return pd.DataFrame( + {"A": [1, 2, 3, 4, 5], "B": [10, 8, 6, 4, 2], "C": [10, 9, 8, 7, 6]} + ) +``` + +To do the same via annotated type hint, you would do: + +```python +@func +async def get_mymodel() -> Annotated[object, {"icon": ObjectHandleIcons.table, "text": "My Model"}]: + return pd.DataFrame( + {"A": [1, 2, 3, 4, 5], "B": [10, 8, 6, 4, 2], "C": [10, 9, 8, 7, 6]} + ) +``` + +To be able to use an object handle as argument in another function, just use the `object` type hint with the argument. A simple `view` function to translate an object handle to Excel values would look like this: + +```python +@func +async def view(obj: object): + return obj +``` + +In the [custom functions examples](https://github.com/xlwings/xlwings-server/blob/main/app/custom_functions/examples.py), you will find a slightly more sophisticated `view` function that optionally allows you to return just the first couple of rows. + +If you are looking for functionality similar to how the `xl()` function works in Microsoft's Python in Excel, you can do it as follows: + +```python +@func +async def to_df(df: pd.DataFrame) -> object: + return df +``` + +This turns an existing Excel range into a DataFrame. Using an Excel table as your source range is a good idea as it makes your object handle dynamically update whenever you resize the Excel table. + +```{note} +- This feature requires xlwings Server v0.5.0+ as well as a Redis/ValKey database for production via `XLWINGS_OBJECT_CACHE_URL`. The object cache is purged once a week, but this can be configured via `XLWINGS_OBJECT_CACHE_EXPIRE_AT`. Alternatively, you'll find a function called `clear_object_cache` in the examples. +- For development purposes, you don't need Redis, but the cache is in-memory and thus only works with a single worker/process for as long as the app runs. More importantly, there won't be any automatic cache purging happening. +``` + +Right now, you can return the majority of Python data types such as simple lists, dictionaries, and tuples. NumPy arrays and pandas DataFrames/Series are also supported. However, more complex objects like a dictionary that holds a pandas DataFrame isn't supported yet. + +The object handles are stored in the cache using a key that derives from the add-in installation, workbook name and cell address, i.e, objects are not shared across different Excel installations or users. + +## Custom functions vs. legacy UDFs + +While Office.js-based custom functions are mostly compatible with the VBA-based UDFs, there are a few differences, which you should be aware of when switching from UDFs to custom functions or vice versa: + +:::{list-table} +:header-rows: 1 + +- - + - Custom functions (Office.js-based) + - User-defined functions UDFs (VBA-based) + +- - Supported platforms + - - Windows + - macOS + - Excel on the web + - - Windows + +- - Empty cells are converted to + - `0` => If you want `None`, you have to set the following formula in Excel: `=""` + - `None` + +- - Cells with integers are converted to + - Integers + - Floats + +- - Reading Date/Time-formatted cells + - Requires the use of `dt.datetime` or `parse_dates` in the arg decorators + - Automatic conversion + +- - Writing datetime objects + - Automatic cell formatting + - No cell formatting + +- - Can write proper Excel cell error + - Yes + - No + +- - Writing `NaN` (`np.nan` or `pd.NA`) arrives in Excel as + - `#NUM!` + - Empty cell + +- - Functions are bound to + - Add-in + - Workbook + +- - Asynchronous functions + - Always and automatically + - Requires `@xw.func(async_mode="threading")` + +- - Decorators + - `from xlwings.server import func, ret, arg`, then `func` etc. + - `import xlwings as xw`, then `xw.func` etc. + +- - Formula Intellisense + - Yes + - No + +- - Supports namespaces e.g., `NAMESPACE.FUNCTION` + - Yes + - No + +- - Capitalization of function name + - Excel formula gets automatically capitalized + - Excel formula has same capitalization as Python function + +- - Supports (SSO) Authentication + - Yes + - No + +- - `caller` function argument + - N/A + - Returns Range object of calling cell + +- - `@xw.arg(vba=...)` + - N/A + - Allows to access Excel VBA objects + +- - Supports pictures + - No + - Yes + +- - Requires a local installation of Python + - No + - Yes + +- - Python code must be shared with end-user + - No + - Yes + +- - Requires License Key + - Yes + - No + +- - License + - PolyForm Noncommercial License 1.0.0 or xlwings PRO License + - BSD 3-clause Open Source License + +::: + +## Limitations + +- Custom functions are only supported with Office.js add-in. +- The Office.js Custom Functions API was introduced in 2018 and therefore requires at least Excel 2021 or Excel 365. +- Note that some functionality requires specific build versions, such as error cells and date formatting, but if your version of Excel doesn't support these features, xlwings will fall back to either string-formatted error messages or unformatted date serials. For more details on which builds support which function, see [Custom Functions requirement sets](https://learn.microsoft.com/en-us/javascript/api/requirement-sets/excel/custom-functions-requirement-sets>). diff --git a/docs/custom_scripts.md b/docs/custom_scripts.md index bf97d305..2a96fb6d 100644 --- a/docs/custom_scripts.md +++ b/docs/custom_scripts.md @@ -1 +1,136 @@ # Custom Scripts + +Custom scripts can be connected to buttons on either the Ribbon or the task pane. They are the equivalent to a `Sub` in VBA. + +## Basic syntax + +As you can see in the [examples](https://github.com/xlwings/xlwings-server/blob/main/app/custom_scripts/examples.py), the simplest custom script requires: + +- the `@script` decorator +- a function argument with the `xw.Book` type hint + +Here is how this looks: + +```python +import xlwings as xw +from xlwings.server import script + +@script +def hello_world(book: xw.Book): + sheet = book.sheets[0] + sheet["A1"].value == "Hello xlwings!" +``` + +```{note} +- The `script` decorator is imported from `xlwings.server` rather than `xlwings`. +- While it's ok to edit the functions in `examples.py` to try things out, you shouldn't commit the changes to Git to prevent future merge conflicts. Rather, create a new Python module as explained in the next section. +``` + +## Adding new custom scripts + +Here is how you can write your own custom scripts: + +1. Add a Python module under [`app/custom_scripts`](https://github.com/xlwings/xlwings-server/blob/main/app/custom_scripts), e.g., `myscripts.py`. +2. Add the following import statement (highlighted line) to [`app/custom_scripts/__init__.py`](https://github.com/xlwings/xlwings-server/blob/main/app/custom_scripts/__init__.py): + +```{code-block} python +:emphasize-lines: 6 + +from ..config import settings + +if settings.enable_examples: + from .examples import * + +from .myscripts import * +``` + +## Office.js add-in + +With Office.js add-ins, you can either bind a button on the ribbon or on the task pane to a custom script. Since placing a button on the task pane is easier, we'll start with that! + +### Bind a script to a button on the task pane + +On the task pane, connecting a button is as easy as adding the `xw-click` attribute with the name of the Python function. You can optionally configure it via `xw-config`: + +```html + +``` + +The default task pane from the examples includes the full code: [`app/templates/examples/hello_world/taskpane_hello.html`](https://github.com/xlwings/xlwings-server/blob/main/app/templates/examples/hello_world/taskpane_hello.html). + +### Bind a script to a button on the Ribbon + +To connect a button on the ribbon to your script, you need a bit more work: + +[`app/templates/manifest.xml`](https://github.com/xlwings/xlwings-server/blob/main/app/templates/manifest.xml) has a section where it defines a ribbon button: + +```xml + + + +