diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..aed1ece --- /dev/null +++ b/.dockerignore @@ -0,0 +1,62 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.venv + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.nox/ + +# Environment +.env +.env.local +.env.*.local + +# Downloads +downloads/ +*.pdf +*.doc +*.docx + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Documentation +*.md +!README.md + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# Other +*.log +.cache diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..8e14b00 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,45 @@ +name: Code quality + +on: + pull_request: + branches: + - main + - master + - develop + push: + branches: + - main + - master + - develop + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-codequality-${{ hashFiles('**/requirements.txt', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-codequality- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -e ".[dev]" + + - name: Run Black (format check) + run: black --check --diff signnow/ tests/ examples/ run_examples.py + + - name: Run Flake8 + run: flake8 signnow/ tests/ examples/ run_examples.py --max-line-length=88 --extend-ignore=E203,W503,E501 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6792f1a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,56 @@ +name: Tests + +on: + pull_request: + branches: + - main + - master + - develop + push: + branches: + - main + - master + - develop + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache pip packages + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt', '**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -e ".[dev]" + + - name: Run tests with coverage + run: | + pytest tests/ -v --cov=signnow --cov-report=term --cov-fail-under=80 + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ matrix.python-version }} + path: | + .pytest_cache/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index c5b92ce..f97851d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,130 @@ -# Compiled python modules. -*.pyc +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -# Setuptools distribution folder. -/dist/ +# C extensions +*.so -# Python egg metadata, regenerated from source files by setuptools. -/*.egg-info +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST -# virtualenv environment -/venv/ +# PyInstaller +*.manifest +*.spec -# Ignore pycharm idea folder. -/.idea/ +# Test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ -# Ignore downloaded documents -/examples/downloaded_documents/ +# Translations +*.mo +*.pot -# Package build directory -/build/ +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fac620e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,143 @@ +# Contributing to signNow Python SDK + +Thank you for your interest in contributing! This guide will help you get started. + +## Prerequisites + +- Python 3.8+ +- pip +- Docker & docker-compose (optional, recommended) + +## What we welcome + +We welcome contributions in these areas: + +- bug fixes +- documentation improvements +- tests +- performance improvements with evidence +- better error handling +- API consistency improvements +- support for new upstream API features +- refactoring that reduces maintenance burden without changing behavior + +We are more cautious about: + +- breaking public API changes +- large stylistic rewrites +- new dependencies +- “helper” abstractions that hide important API behavior +- speculative features that the upstream API does not support + +The reason is simple: SDK users usually value **stability and clarity** more than novelty. + +## Development workflow + +### 1. Create a branch + +```bash +git checkout -b your-feature-branch +``` + +### 2. Make changes + +All SDK source lives under `signnow/`. Each API domain is a sub-package under +`signnow/api/` with `request/` and `response/` directories. + +### 3. Format & lint + +```bash +make lint # check formatting + Flake8 +make format # auto-format with Black +``` + +### 4. Run tests + +```bash +make test # all unit tests +make test-cov # with coverage report (htmlcov/) +make test-api # only mock-API integration tests +``` + +Or run the full CI check in one command: + +```bash +make check # lint + typecheck + tests +``` + +Tests do **not** require a `.env` file — they use mocked HTTP responses. + +### 5. Docker (optional) + +```bash +make docker-build +make docker-test +``` + +## Project conventions + +### Code style + +- **Formatter:** [Black](https://github.com/psf/black) with default 88-char line length. +- **Linter:** [Flake8](https://flake8.pycqa.org/) (E203/W503/E501 ignored). +- **Type hints:** Use them on all public APIs. Run `make typecheck` (mypy) to verify. + +### Naming + +| Item | Convention | Example | +|------|-----------|---------| +| Request class | `{Entity}{Method}Request` | `DocumentPostRequest` | +| Response class | `{Entity}{Method}Response` | `DocumentPostResponse` | +| Request file | `{entity}_{method}_request.py` | `document_post_request.py` | +| Response file | `{entity}_{method}_response.py` | `document_post_response.py` | +| Test file | `test_{domain}.py` | `test_document.py` | + +### Request/response pattern + +Every API endpoint is a pair of request + response classes: + +```python +# signnow/api/example/request/thing_get_request.py +from signnow.core.request import RequestInterface, api_endpoint + +@api_endpoint( + name="getThing", + url="/thing/{thing_id}", + method="get", + auth="bearer", + namespace="example", + entity="thing", +) +class ThingGetRequest(RequestInterface): + def __init__(self): + self._uri_params = {} + + def with_thing_id(self, thing_id: str) -> "ThingGetRequest": + self._uri_params["thing_id"] = thing_id + return self + + def uri_params(self): + return dict(self._uri_params) + + def payload(self): + return {} +``` + +### Tests + +- One test file per API domain in `tests/api/`. +- Test request creation, response structure, and mock API calls. +- Use the `mock_api_client` fixture from `conftest.py` for API-call tests. + +## Submitting a pull request + +1. Ensure `make check` passes. +2. Write or update tests for your changes. +3. Keep commits focused — one logical change per commit. +4. Open a PR against `master` with a clear description. + +## Reporting issues + +Open an issue on GitHub with: +- Minimal reproduction steps +- Full traceback if applicable diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..440b97f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# SignNow Python SDK Dockerfile +FROM python:3.11-slim + +# Set working directory +WORKDIR /app + +# Set environment variables +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the entire SDK +COPY . . + +# Install SDK in development mode +RUN pip install --no-cache-dir -e . + +# Create downloads directory +RUN mkdir -p /app/downloads + +# Default command (can be overridden in docker-compose) +CMD ["python", "--version"] diff --git a/LICENSE b/LICENSE index 261eeb9..87d7ae0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2024 SignNow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..17dfc55 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.md +include LICENSE +include requirements.txt +include env.example diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a2c4672 --- /dev/null +++ b/Makefile @@ -0,0 +1,174 @@ +.PHONY: help install test test-cov test-api examples clean setup-env lint format check typecheck venv docker-build docker-up docker-shell docker-down docker-test docker-test-cov docker-test-api docker-examples docker-example docker-example-auth docker-example-user docker-example-doc + +help: + @echo "SignNow Python SDK - Makefile commands:" + @echo "" + @echo "=== Setup ===" + @echo " make venv - Create virtualenv and install all deps" + @echo " make install - Install dependencies into current env" + @echo " make setup-env - Create .env file from example" + @echo "" + @echo "=== Code quality ===" + @echo " make format - Format code with Black" + @echo " make lint - Run Black (check) and Flake8" + @echo " make typecheck - Run mypy type checks" + @echo " make check - Run lint + typecheck + tests (full CI)" + @echo "" + @echo "=== Tests ===" + @echo " make test - Run all unit tests" + @echo " make test-cov - Run tests with coverage report" + @echo " make test-api - Run only mock-API integration tests" + @echo "" + @echo "=== Docker ===" + @echo " make docker-build - Build Docker image" + @echo " make docker-up - Start Docker container" + @echo " make docker-shell - Open shell in Docker container" + @echo " make docker-down - Stop Docker container" + @echo " make docker-test - Run all tests in Docker" + @echo " make docker-test-cov - Run tests with coverage in Docker" + @echo " make docker-test-api - Run only real API tests in Docker" + @echo "" + @echo "=== Examples ===" + @echo " make examples - Run all examples" + @echo " make docker-examples - Run all examples in Docker" + @echo " make docker-example EXAMPLE=auth_check_example.py - Run one example" + @echo "" + @echo "=== Other ===" + @echo " make clean - Remove caches, build artefacts, coverage" + @echo "" + +install: + @echo "Installing dependencies..." + pip install -r requirements.txt + pip install -e ".[dev]" + +venv: + @echo "Creating virtualenv and installing all deps..." + python3 -m venv venv + . venv/bin/activate && pip install --upgrade pip && pip install -r requirements.txt && pip install -e ".[dev]" + @echo "Done. Activate with: source venv/bin/activate" + +lint: + @echo "Running code quality checks..." + black --check --diff signnow/ tests/ examples/ run_examples.py + flake8 signnow/ tests/ examples/ run_examples.py --max-line-length=88 --extend-ignore=E203,W503,E501 + @echo "Lint passed." + +format: + @echo "Formatting code with Black..." + black signnow/ tests/ examples/ run_examples.py + @echo "Done." + +typecheck: + @echo "Running mypy type checks..." + mypy signnow/ --ignore-missing-imports --no-error-summary || true + +check: lint typecheck test + @echo "All checks passed." + +setup-env: + @echo "Creating .env file..." + @if [ ! -f .env ]; then \ + if [ -f env.example ]; then \ + cp env.example .env; \ + echo ".env file created from env.example! Please edit it and add your credentials."; \ + else \ + echo "# SignNow API Configuration" > .env; \ + echo "# Copy this file to .env and fill in your credentials" >> .env; \ + echo "" >> .env; \ + echo "SIGNNOW_API_HOST=https://api.signnow.com" >> .env; \ + echo "SIGNNOW_API_BASIC_TOKEN=your_basic_token_here" >> .env; \ + echo "SIGNNOW_API_USERNAME=your_username_here" >> .env; \ + echo "SIGNNOW_API_PASSWORD=your_password_here" >> .env; \ + echo "SIGNNOW_DOWNLOADS_DIR=./downloads" >> .env; \ + echo ".env file created! Please edit it and add your credentials."; \ + fi \ + else \ + echo ".env file already exists."; \ + fi + +test: + @echo "Running tests..." + pytest tests/ -v + +test-cov: + @echo "Running tests with coverage..." + pytest tests/ --cov=signnow --cov-report=html --cov-report=term + @echo "Coverage report generated in htmlcov/index.html" + +test-api: + @echo "Running real API tests..." + pytest tests/api/ -k "api_call" -v + +examples: + @echo "Running all examples..." + python run_examples.py + +example-auth: + @echo "Running token check example..." + python examples/auth_check_example.py + +example-doc: + @echo "Running document get example..." + python examples/document_get_example.py + +example-user: + @echo "Running user info example..." + python examples/user_info_example.py + +clean: + @echo "Cleaning temporary files..." + find . -type d -name "__pycache__" -exec rm -r {} + 2>/dev/null || true + find . -type f -name "*.pyc" -delete + find . -type f -name "*.pyo" -delete + find . -type d -name "*.egg-info" -exec rm -r {} + 2>/dev/null || true + find . -type d -name ".pytest_cache" -exec rm -r {} + 2>/dev/null || true + find . -type d -name "htmlcov" -exec rm -r {} + 2>/dev/null || true + find . -type d -name ".coverage" -exec rm -r {} + 2>/dev/null || true + @echo "Cleanup complete!" + +docker-build: + @echo "Building Docker image..." + docker-compose build + +docker-up: + @echo "Starting Docker container..." + docker-compose up -d signnow-sdk + @echo "Container started. Use 'make docker-shell' to enter." + +docker-shell: + @echo "Entering Docker container..." + docker-compose exec signnow-sdk bash + +docker-down: + @echo "Stopping Docker container..." + docker-compose down + +docker-test: + @echo "Running tests in Docker..." + docker-compose --profile test run --rm test + +docker-test-cov: + @echo "Running tests with coverage in Docker..." + docker-compose --profile test run --rm test-cov + @echo "" + @echo "Coverage report generated in htmlcov/index.html" + @echo "Open htmlcov/index.html in browser to view" + +docker-test-api: + @echo "Running real API tests in Docker..." + docker-compose --profile test run --rm test-api + +docker-examples: + @echo "Running all examples in Docker..." + docker-compose --profile examples run --rm run-examples + +docker-example: +ifndef EXAMPLE + @echo "Usage: make docker-example EXAMPLE=" + @echo " e.g. make docker-example EXAMPLE=auth_check_example.py" + @echo " e.g. make docker-example EXAMPLE=auth_check_example" + exit 1 +endif + @echo "Running example $(EXAMPLE) in Docker..." + docker-compose --profile examples run --rm run-examples python run_examples.py $(EXAMPLE) diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d74b8e --- /dev/null +++ b/README.md @@ -0,0 +1,301 @@ +# signNow API Python SDK +## v3.0.0 + +[![Python Version](https://img.shields.io/badge/codebase-python--3.8+-blue)](https://www.python.org/) + +Package metadata and build configuration follow [PEP 517/518](https://peps.python.org/pep-0517/) and live in `pyproject.toml`. + +### About SignNow +SignNow is a powerful web-based e-signature solution that streamlines the signing process and overall document flow for businesses of any size. SignNow offers SaaS as well as public and private cloud deployment options using the same underlying API. With SignNow you can easily sign, share and manage documents in compliance with international data laws and industry-specific regulations. SignNow enables you to collect signatures from partners, employees and customers from any device within minutes. For more details, please, visit [SignNow API Reference](https://docs.signnow.com/docs/signnow/welcome). + +### Requirements +- Python 3.8+ +- pip + +### Quick Start + +#### Option 1: Local installation + +```bash +# 1. Installation +cd python-sdk +python3 -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install -r requirements.txt +pip install -e . + +# 2. Configuration (create .env file) +cp env.example .env +# Edit .env file: add your credentials and basic token (see details in Credentials section below) + +# 3. Run example +python examples/auth_check_example.py +``` + +#### Option 2: Docker (recommended) + +```bash +# 1. Configuration +cd python-sdk +cp env.example .env +# Edit .env file: add your credentials and basic token (see details in Credentials section below) + +# 2. Build and run +docker-compose build +docker-compose up -d signnow-sdk +docker-compose exec signnow-sdk bash + +# 3. Inside the container +python examples/auth_check_example.py +``` + +### Installation + +#### From source +```bash +git clone https://github.com/signnow/PythonSdk.git +cd python-sdk +pip install -r requirements.txt +pip install -e . +``` + +#### Using pip (when published) +```bash +pip install signnow-python-sdk +``` + +### Configuration + +Copy `env.example` to `.env` and fill in your credentials: + +```bash +cp env.example .env +``` + +Then edit `.env` with your credentials: + +```bash +SIGNNOW_API_HOST=https://api.signnow.com +SIGNNOW_API_BASIC_TOKEN=your_basic_token +SIGNNOW_API_USERNAME=your_username +SIGNNOW_API_PASSWORD=your_password +SIGNNOW_DOWNLOADS_DIR=./downloads +``` + +Alternatively, you can set these as environment variables. + +### Credentials +To run examples or use the SDK in your project, you will need API keys. Follow these steps to obtain them: + +1. Register for an account on SignNow [here](https://www.signnow.com/api) +2. Create a new application. +3. Obtain the Basic Authentication API token for your application. +4. Add your email, password, and the Basic token to the .env file. + +Now, you are ready to use the SDK. + +### Code quality +CI runs [Black](https://github.com/psf/black) (format check) and [Flake8](https://flake8.pycqa.org/) (lint) on push/PR. Run locally: + +```bash +pip install -e ".[dev]" +make format # format code with Black +make lint # check format and run Flake8 +``` + +### Run tests + +#### Quick start +```bash +# Install dev dependencies once +pip install -r requirements.txt +pip install -e ".[dev]" + +# Run all the tests +pytest tests/ -v +# or +make test + +# Run with coverage report +pytest tests/ --cov=signnow --cov-report=html --cov-report=term +# or +make test-cov +``` + +#### Docker (recommended) +```bash +make docker-test # all tests in Docker +make docker-test-cov # with coverage +``` + +> **Note:** Tests do not require a `.env` file — they use mocked HTTP responses. +> Only the `examples/` scripts require real credentials. + +See `make help` for the full list of available targets. + +### Error Handling + +All SDK errors are raised as `SignNowApiException`. The exception carries structured +context you can inspect: + +```python +from signnow.core.exception import SignNowApiException + +try: + response = client.send(request).get_response() +except SignNowApiException as e: + print(e) # human-readable summary + print(e.endpoint) # e.g. "POST /oauth2/token" + print(e.response_code) # e.g. 401 + print(e.response) # raw response body (str) + print(e.payload) # request body that was sent + print(e.cause) # underlying exception, if any +``` + +Common status codes: + +| Code | Meaning | Typical cause | +|------|---------|---------------| +| 401 | Unauthorized | Invalid or expired token / wrong Basic token | +| 403 | Forbidden | Insufficient permissions for the resource | +| 404 | Not Found | Wrong document/template/group ID | +| 422 | Unprocessable | Validation error (missing fields, bad values) | + +### Usage + +#### Basic Authentication Example +```python +from signnow.api.user.request import UserGetRequest +from signnow.api.user.response import UserGetResponse +from signnow.core.api_client import ApiClient +from signnow.core.exception import SignNowApiException +from signnow.core.factory import SdkFactory + +try: + # Create a new instance of the API client containing a freshly created bearer token + client: ApiClient = SdkFactory.create_api_client() + + # Get user information + request = UserGetRequest() + response: UserGetResponse = client.send(request).get_response() + + print(f"User ID: {response.id}") + print(f"User name: {response.first_name} {response.last_name}") +except SignNowApiException as e: + print(f"ERROR: {e}") +``` + +#### Using an Existing Bearer Token +```python +from signnow.api.user.request import UserGetRequest +from signnow.api.user.response import UserGetResponse +from signnow.core.api_client import ApiClient +from signnow.core.exception import SignNowApiException +from signnow.core.factory import SdkFactory + +try: + # Create a new instance without authentication + client: ApiClient = SdkFactory.create_api_client_with_bearer_token("your_bearer_token") + + # Get user information + request = UserGetRequest() + response: UserGetResponse = client.send(request).get_response() + + print(f"User ID: {response.id}") + print(f"User name: {response.first_name} {response.last_name}") +except SignNowApiException as e: + print(f"ERROR: {e}") +``` + +#### Using the SDK Class Directly +```python +from signnow.api.document.request import DocumentGetRequest +from signnow.api.document.response import DocumentGetResponse +from signnow.core.exception import SignNowApiException +from signnow.sdk import Sdk + +try: + # Create SDK instance + sdk = Sdk() + client = sdk.build().authenticate().get_api_client() + + # Get document + request = DocumentGetRequest() + request.with_document_id("your_document_id") + response: DocumentGetResponse = client.send(request).get_response() + + print(f"Document ID: {response.id}") + print(f"Document Name: {response.document_name}") +except SignNowApiException as e: + print(f"ERROR: {e}") +``` + +### Examples +You can find more examples of API usage in the [examples](./examples) directory. +You may also want to check [examples/preset_data.py](./examples/preset_data.py) and fill in your document IDs, template IDs, etc. before running them. +If you already have a bearer token (e.g. from running [auth_check_example.py](./examples/auth_check_example.py)), you can save it as `PRESET_BEARER_TOKEN` in [preset_data.py](./examples/preset_data.py) so other examples reuse it automatically. + +### Project Structure +``` +python-sdk/ +├── signnow/ +│ ├── __init__.py +│ ├── sdk.py # Main SDK class +│ ├── core/ # Core modules +│ │ ├── api_client.py # HTTP client for API requests +│ │ ├── config.py # Configuration management +│ │ ├── exception.py # Custom exceptions +│ │ ├── factory.py # SDK factory +│ │ ├── request.py # Request interface and decorators +│ │ ├── response.py # Response parser +│ │ ├── token.py # Token classes +│ │ └── collection/ # Typed collection utilities +│ └── api/ # API modules +│ ├── auth/ # OAuth2 authentication +│ ├── document/ # Document CRUD, download, merge +│ ├── documentfield/ # Prefill text fields +│ ├── documentgroup/ # Document groups +│ ├── documentgroupinvite/ # Group invite management +│ ├── documentgrouptemplate/ # Group templates +│ ├── documentinvite/ # Field & free-form invites, signing links +│ ├── embeddededitor/ # Embedded editor links +│ ├── embeddedgroupinvite/ # Embedded group invites +│ ├── embeddedinvite/ # Embedded signing invites +│ ├── embeddedsending/ # Embedded sending links +│ ├── folder/ # Folder operations +│ ├── proxy/ # Proxy file/JSON responses +│ ├── smartfields/ # Smart field prefill +│ ├── template/ # Templates, routing, bulk invite +│ ├── user/ # User management +│ ├── webhook/ # Webhook subscriptions (v1) +│ └── webhookv2/ # Event subscriptions (v2) +├── examples/ # Runnable usage examples +├── tests/ # Unit tests +├── pyproject.toml # Build config & dev dependencies +├── Makefile # Dev/test workflow targets +├── Dockerfile # Container image +├── docker-compose.yml # Docker services +└── README.md +``` + +### Features +- ✅ Modern Python 3.8+ syntax with type hints +- ✅ Dataclasses for request/response models +- ✅ Decorator-based API endpoint definition +- ✅ Automatic authentication & token refresh +- ✅ Environment variable and file-based configuration +- ✅ Structured error handling via `SignNowApiException` +- ✅ Streaming file downloads (constant memory usage) +- ✅ Document groups & group invites +- ✅ Embedded editor, signing & sending links +- ✅ Webhook subscriptions (v1 & v2) +- ✅ Smart fields & template routing +- ✅ Bulk invite via CSV +- ✅ Docker-based development workflow +- ✅ Full unit test suite + +### License +This project is licensed under the MIT License - see the LICENSE file for details. + +### Contributing +Contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. diff --git a/README.rst b/README.rst deleted file mode 100644 index 4c2fdfb..0000000 --- a/README.rst +++ /dev/null @@ -1,365 +0,0 @@ -SignNow -======= - -SignNow Python SDK - -About SignNow -------------- - -SignNow is a powerful web-based e-signature solution that streamlines the signing process and overall document flow for businesses of any size. SignNow offers SaaS as well as public and private cloud deployment options using the same underlying API. With SignNow you can easily sign, share and manage documents in compliance with international data laws and industry-specific regulations. SignNow enables you to collect signatures from partners, employees and customers from any device within minutes. - -API Contact Information ------------------------ - -If you have questions about the SignNow API, please visit [https://help.signnow.com/docs](https://help.signnow.com/docs) or email [api@signnow.com](mailto:api@signnow.com). - -See additional contact information at the bottom. - -Installation -============ - -To install to Python library: - -:: - - pip install signnow-python-sdk - -Or clone the SignNow library and extract it into the location of your -choice. - -Navigate to the extracted file and run the following: - -:: - - python setup.py install - -Setup -===== - -.. code:: python - - import signnow_python_sdk - - signnow_python_sdk.Config(client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET", environment="production") - - Environments: - production - https://api.signnow.com - eval - https://api-eval.signnow.com - -Examples -======== - -To run the examples you will need an API key. You can get one here [https://www.signnow.com/api](https://www.signnow.com/api). For a full list of accepted parameters, refer to the SignNow REST Endpoints API guide: [https://help.signnow.com/docs](https://help.signnow.com/docs). - -OAuth2 -====== - -Request OAuth Token -------------------- - -.. code:: python - - access_token = signnow_python_sdk.OAuth2.request_token("YOUR USERNAME", "YOUR PASSWORD") - -Verify OAuth Token ------------------- - -.. code:: python - - access_token_verify = signnow_python_sdk.OAuth2.verify(AccessToken) - -User -==== - -Create New User ---------------- - -.. code:: python - - new_user = signnow_python_sdk.User.create("name@domain.com", "newpassword", "Firstname", "Lastname") - -Retreive User Account Information ---------------------------------- - -.. code:: python - - sn_user = signnow_python_sdk.User.get(access_token) - -Document -======== - -Get Document ------------- - -.. code:: python - - # without annotations - document_data = signnow_python_sdk.Document.get(access_token, "YOUR_DOCUMENT_ID") - - # with annotations - document_data = signnow_python_sdk.Document.get(access_token, "YOUR_DOCUMENT_ID", True) - -Create New Document -------------------- - -.. code:: python - - dir_path = os.path.dirname(os.path.realpath(__file__)) + '/testing123.pdf' - doc_id = signnow_python_sdk.Document.upload(access_token, dir_path, False) - -Create New Document and Extract the Fields ------------------------------------------- - -.. code:: python - - dir_path = os.path.dirname(os.path.realpath(__file__)) + '/testing123.pdf' - doc_id = signnow_python_sdk.Document.upload(access_token, dir_path) - -Update Document ---------------- - -.. code:: python - - update_payload = { - "texts": [ - { - "size": 22, - "x": 61, - "y": 72, - "page_number": 0, - "font": "Arial", - "data": "a sample text element", - "line_height": 9.075, - "client_timestamp": datetime.now().strftime("%s") - } - ], - fields: [ - { - "x": 10, - "y: 10, - "width": 122, - "height": 34, - "page_number": 0, - "role": "Buyer", - "required": True, - "type": "signature" - } - ] - } - - update_doc_res = signnow_python_sdk.Document.update(access_token, doc_id, update_payload) - -Delete Document ---------------- - -.. code:: python - - delete_doc_res = signnow_python_sdk.Document.delete(access_token, doc_id) - -Download Document ------------------ - -.. code:: python - - # without history - download_doc_res = signnow_python_sdk.Document.download(access_token, "YOUR DOCUMENT ID", "/", "sample") - - # with history - download_doc_res = signnow_python_sdk.Document.download(access_token, "YOUR DOCUMENT ID", "/", "sample", True) - -Send Free Form Invite ---------------------- - -.. code:: python - - invite_payload = new - { - "from": "account_email@domain.com", - "to": "name@domain.com" - } - - freeform_invite_res = signnow_python_sdk.Document.invite(access_token, "YOUR DOCUMENT ID", invite_payload) - -Send Role-based Invite ----------------------- - -.. code:: python - - invite_payload = { - "to": [ - { - "email": "name@domain.com", - "role_id": "", - "role": "Role 1", - "order": 1, - "authentication_type": "password", - "password": "SOME PASSWORD", - "expiration_days": 15, - "reminder": 5 - }, - { - "email": "name@domain.com", - "role_id": "", - "role": "Role 2", - "order": 2, - "authentication_type": "password", - "password": "SOME PASSWORD", - "expiration_days": 30, - "reminder": 10 - } - ], - "from": "your_account_email@domain.com", - "cc": [ - "name@domain.com" - ], - "subject": "YOUR SUBJECT", - "message": "YOUR MESSAGE" - }; - - role_based_invite_res = signnow_python_sdk.Document.invite(access_token, "YOUR DOCUMENT ID", invite_payload) - -Cancel Invite -------------- - -.. code:: python - - cancel_invite_res = signnow_python_sdk.Document.cancel_invite(access_token, "YOUR DOCUMENT ID"); - -Merge Existing Documents ------------------------- - -.. code:: python - - merge_doc_payload = { - "name": "My New Merged Doc", - "document_ids": ["YOUR DOCUMENT ID", "YOUR DOCUMENT ID"] - } - - merge_doc_res = signnow_python_sdk.Document.merge_and_download(access_token, mergeDocsObj, "/", "sample-merge"); - -Document History ----------------- - -.. code:: python - - doc_history_res = signnow_python_sdk.Document.get_history(access_token, "YOUR DOCUMENT ID"); - -Template -======== - -Create Template ---------------- - -.. code:: python - - new_template_res = signnow_python_sdk.Template.create(access_token, "YOUR DOCUMENT ID", "My New Template"); - -Copy Template -------------- - -.. code:: python - - copy_template_res = signnow_python_sdk.Template.copy(access_token, "YOUR TEMPLATE ID", "My Copy Template Doc"); - -Folder -====== - -+------------------------+-----------------------------------------------------------------------+ -| Filters | Values | -+========================+=======================================================================+ -| ``signing-status`` | ``waiting-for-me``, ``waiting-for-others``, ``signed``, ``pending`` | -+------------------------+-----------------------------------------------------------------------+ -| ``document-updated`` | ``datetime.now().strftime("%s")`` | -+------------------------+-----------------------------------------------------------------------+ -| ``document-created`` | ``datetime.now().strftime("%s")`` | -+------------------------+-----------------------------------------------------------------------+ - -+---------------------+--------------------+ -| Sort | Values | -+=====================+====================+ -| ``document-name`` | ``asc``/``desc`` | -+---------------------+--------------------+ -| ``updated`` | ``asc``/``desc`` | -+---------------------+--------------------+ -| ``created`` | ``asc``/``desc`` | -+---------------------+--------------------+ - -Get users root folder ---------------------- - -.. code:: python - - root_folder_Res = signnow_python_sdk.Folder.root_folder(access_token); - -Get Folder ----------- - -.. code:: python - - get_folder_res = signnow_python_sdk.Folder.get(access_token, "YOUR FOLDER ID"); - -Webhook -======= - -Create Webhook --------------- - -+-----------------------+-------------------------------------------------------------------------------------------------------------+ -| Events | Description | -+=======================+=============================================================================================================+ -| ``document.create`` | Webhook is triggered when a document is uploaded to users account in SignNow | -+-----------------------+-------------------------------------------------------------------------------------------------------------+ -| ``document.update`` | Webhook is triggered when a document is updated (fields added, text added, signature added, etc.) | -+-----------------------+-------------------------------------------------------------------------------------------------------------+ -| ``document.delete`` | Webhook is triggered when a document is deleted from | -+-----------------------+-------------------------------------------------------------------------------------------------------------+ -| ``invite.create`` | Webhook is triggered when an invitation to a SignNow document is created. | -+-----------------------+-------------------------------------------------------------------------------------------------------------+ -| ``invite.update`` | Webhook is triggered when an invite to Signnow document is updated. Ex. A signer has signed the document. | -+-----------------------+-------------------------------------------------------------------------------------------------------------+ - -.. code:: python - - createWebhookRes = signnow_python_sdk.Webhook.create(access_token, "document.create", "YOUR URL"); - -List Webhooks -------------- - -.. code:: python - - list_webhooks_res = signnow_python_sdk.Webhook.list_all(access_token); - -Delete Webhook --------------- - -.. code:: python - - delete_webhook_res = signnow_python_sdk.Webhook.delete(AccessToken, "YOUR WEBHOOK ID"); - -Link -==== - -Create Link ------------ - -.. code:: python - - create_link_res = signnow_python_sdk.Link.create(access_token, "YOUR DOCUMENT ID"); - -LICENSE -------- - -This project is released under the Apache 2.0 [License](https://github.com/signnow/SNPythonSDK/blob/master/LICENSE.md). - -Additional Contact Information -============================== - -SUPPORT -------- - -To contact SignNow support, please email [support@signnow.com](mailto:support@signnow.com). - -SALES ------ - -For pricing information please call (800) 831-2050 or email [sales@signnow.com](mailto:sales@signnow.com). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ad174dd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,120 @@ +version: '3.8' + +services: + signnow-sdk: + build: + context: . + dockerfile: Dockerfile + container_name: signnow-python-sdk + volumes: + # Mount .env file (if exists) + - ./.env:/app/.env:ro + # Mount downloads directory + - ./downloads:/app/downloads + # Mount SDK source code for development + - ./signnow:/app/signnow + # Mount examples for easy access + - ./examples:/app/examples + # Mount tests + - ./tests:/app/tests + # Mount setup files + - ./setup.py:/app/setup.py + - ./requirements.txt:/app/requirements.txt + - ./check_setup.py:/app/check_setup.py + environment: + # Pass environment variables if .env doesn't exist + - SIGNNOW_API_HOST=${SIGNNOW_API_HOST:-https://api.signnow.com} + - SIGNNOW_API_BASIC_TOKEN=${SIGNNOW_API_BASIC_TOKEN:-} + - SIGNNOW_API_USERNAME=${SIGNNOW_API_USERNAME:-} + - SIGNNOW_API_PASSWORD=${SIGNNOW_API_PASSWORD:-} + - SIGNNOW_DOWNLOADS_DIR=${SIGNNOW_DOWNLOADS_DIR:-./downloads} + working_dir: /app + # Keep container running for interactive use + stdin_open: true + tty: true + # Default command - interactive shell + command: /bin/bash + + # Service to run all examples via run_examples.py + run-examples: + build: + context: . + dockerfile: Dockerfile + container_name: signnow-run-examples + volumes: + - ./.env:/app/.env:ro + - ./downloads:/app/downloads + - ./signnow:/app/signnow + - ./examples:/app/examples + - ./run_examples.py:/app/run_examples.py + - ./setup.py:/app/setup.py + - ./requirements.txt:/app/requirements.txt + environment: + - SIGNNOW_API_HOST=${SIGNNOW_API_HOST:-https://api.signnow.com} + - SIGNNOW_API_BASIC_TOKEN=${SIGNNOW_API_BASIC_TOKEN:-} + - SIGNNOW_API_USERNAME=${SIGNNOW_API_USERNAME:-} + - SIGNNOW_API_PASSWORD=${SIGNNOW_API_PASSWORD:-} + - SIGNNOW_DOWNLOADS_DIR=${SIGNNOW_DOWNLOADS_DIR:-./downloads} + working_dir: /app + command: python run_examples.py + profiles: + - examples + + # Service to run tests + test: + build: + context: . + dockerfile: Dockerfile + container_name: signnow-sdk-test + volumes: + - ./.env:/app/.env:ro + - ./downloads:/app/downloads + - ./signnow:/app/signnow + - ./tests:/app/tests + - ./setup.py:/app/setup.py + - ./pyproject.toml:/app/pyproject.toml + - ./requirements.txt:/app/requirements.txt + - ./htmlcov:/app/htmlcov # For coverage reports + working_dir: /app + command: pytest tests/ -v + profiles: + - test + + # Service to run tests with coverage + test-cov: + build: + context: . + dockerfile: Dockerfile + container_name: signnow-sdk-test-cov + volumes: + - ./.env:/app/.env:ro + - ./downloads:/app/downloads + - ./signnow:/app/signnow + - ./tests:/app/tests + - ./setup.py:/app/setup.py + - ./pyproject.toml:/app/pyproject.toml + - ./requirements.txt:/app/requirements.txt + - ./htmlcov:/app/htmlcov + working_dir: /app + command: pytest tests/ --cov=signnow --cov-report=html --cov-report=term -v + profiles: + - test + + # Service to run only API call tests + test-api: + build: + context: . + dockerfile: Dockerfile + container_name: signnow-sdk-test-api + volumes: + - ./.env:/app/.env:ro + - ./downloads:/app/downloads + - ./signnow:/app/signnow + - ./tests:/app/tests + - ./setup.py:/app/setup.py + - ./pyproject.toml:/app/pyproject.toml + - ./requirements.txt:/app/requirements.txt + working_dir: /app + command: pytest tests/api/ -k "api_call" -v + profiles: + - test diff --git a/env.example b/env.example new file mode 100644 index 0000000..8007e90 --- /dev/null +++ b/env.example @@ -0,0 +1,21 @@ +# SignNow API Configuration +# Copy this file to .env and fill in your credentials +# +# To get your credentials: +# 1. Register for an account on SignNow: https://www.signnow.com/api +# 2. Create a new application +# 3. Obtain the Basic Authentication API token for your application +# 4. Fill in the values below + +# SignNow API host (default: https://api.signnow.com) +SIGNNOW_API_HOST=https://api.signnow.com + +# This is your application's Basic token from SignNow +SIGNNOW_API_BASIC_TOKEN=your_basic_token_here + +# Your SignNow account email and password +SIGNNOW_API_USERNAME=your_email@example.com +SIGNNOW_API_PASSWORD=your_password_here + +# Directory where downloaded files will be saved +SIGNNOW_DOWNLOADS_DIR=./downloads diff --git a/examples/02_create_template.py b/examples/02_create_template.py deleted file mode 100644 index dbaca54..0000000 --- a/examples/02_create_template.py +++ /dev/null @@ -1,75 +0,0 @@ -import signnow_python_sdk -from datetime import datetime -import os - -if __name__ == '__main__': - signnow_python_sdk.Config(client_id="CLIENT_ID", - client_secret="CLIENT_SECRET", - environment="production") - - # Enter your own credentials - username = "USER_NAME" - password = "USER_PASSWORD" - - # Create access_token for the user - access_token = signnow_python_sdk.OAuth2.request_token(username, password, '*') - - # Upload a new document - dir_path = os.path.dirname(os.path.realpath(__file__)) + '/testing123.pdf' - doc_id = signnow_python_sdk.Document.upload(access_token['access_token'], dir_path) - - # Convert document to a template - template_id = signnow_python_sdk.Template.create(access_token['access_token'], doc_id['id'], "My New Template") - template_data = signnow_python_sdk.Document.get(access_token['access_token'], template_id['id']) - - # Create the PUT /document payload - doc_payload = { - "texts": [ - { - "size": 22, - "x": 61, - "y": 72, - "page_number": 0, - "font": "Arial", - "data": "a sample text element", - "line_height": 9.075, - "client_timestamp": datetime.now().strftime("%s") - } - ], - "fields": [ - { - "x": 61, - "y": 100, - "width": 120, - "height": 34, - "page_number": 0, - "role": "Signer 1", - "required": True, - "type": "signature" - }, - { - "x": 61, - "y": 160, - "width": 120, - "height": 12, - "page_number": 0, - "label": "New Label", - "role": "Signer 1", - "required": True, - "type": "text" - }, - { - "x": 61, - "y": 178, - "width": 41, - "height": 26, - "page_number": 0, - "role": "Signer 2", - "required": True, - "type": "initials" - } - ] - } - - # Add fields and texts to the template - put_doc_response = signnow_python_sdk.Template.update(access_token['access_token'], template_id['id'], doc_payload) diff --git a/examples/03_template_invite.py b/examples/03_template_invite.py deleted file mode 100644 index 9d1c732..0000000 --- a/examples/03_template_invite.py +++ /dev/null @@ -1,37 +0,0 @@ -import signnow_python_sdk - -if __name__ == '__main__': - signnow_python_sdk.Config(client_id="CLIENT_ID", - client_secret="CLIENT_SECRET", - environment="production") - - # Enter your own credentials - username = "USER_NAME" - password = "USER_PASSWORD" - template_id = "TEMPLATE_ID" - - # Create access_token for the user - access_token = signnow_python_sdk.OAuth2.request_token(username, password, '*') - - to = [{ - "email": "testemail@signnow.com", - "role_id": "", - "role": "Signer 1", - "order": 1 - }] - - invite_payload = { - "to": to, - "from": username - } - - # Create a document from the template - doc_id = signnow_python_sdk.Template.copy(access_token['access_token'], template_id, "New Doc From Template") - - # Get document data - document_data = signnow_python_sdk.Document.get(access_token['access_token'], doc_id['id']) - - # Send signature invite - invite_response = signnow_python_sdk.Document.invite(access_token['access_token'], doc_id['id'], invite_payload) - - diff --git a/examples/05_template_invite_multiple_roles.py b/examples/05_template_invite_multiple_roles.py deleted file mode 100644 index f4d0086..0000000 --- a/examples/05_template_invite_multiple_roles.py +++ /dev/null @@ -1,43 +0,0 @@ -import signnow_python_sdk - -if __name__ == '__main__': - signnow_python_sdk.Config(client_id="CLIENT_ID", - client_secret="CLIENT_SECRET", - environment="production") - - # Enter your own credentials - username = "USER_NAME" - password = "USER_PASSWORD" - template_id = "TEMPLATE_ID" - - # Create access_token for the user - access_token = signnow_python_sdk.OAuth2.request_token(username, password, '*') - - to = [{ - "email": "testemail@signnow.com", - "role_id": "", - "role": "Signer 1", - "order": 1 - }, - { - "email": "testemail1@signnow.com", - "role_id": "", - "role": "Signer 2", - "order": 2 - - }] - - invite_payload = { - "to": to, - "from": username - } - - # Create a document from the template - doc_id = signnow_python_sdk.Template.copy(access_token['access_token'], template_id, "New Doc From Template") - - # Get document data - document_data = signnow_python_sdk.Document.get(access_token['access_token'], doc_id['id']) - - # Send signature invite - invite_response = signnow_python_sdk.Document.invite(access_token['access_token'], doc_id['id'], invite_payload) - diff --git a/examples/07_signing_link.py b/examples/07_signing_link.py deleted file mode 100644 index 6948145..0000000 --- a/examples/07_signing_link.py +++ /dev/null @@ -1,17 +0,0 @@ -import signnow_python_sdk - -if __name__ == '__main__': - signnow_python_sdk.Config(client_id="CLIENT_ID", - client_secret="CLIENT_SECRET", - environment="production") - - # Enter your own credentials - username = "USER_NAME" - password = "USER_PASSWORD" - document_id = "DOCUMENT_ID" - - # Create access_token for the user - access_token = signnow_python_sdk.OAuth2.request_token(username, password, '*') - - # Create the signing link for a document. - link = signnow_python_sdk.Link.create(access_token['access_token'], document_id) diff --git a/examples/08_embedded_signing_link.py b/examples/08_embedded_signing_link.py deleted file mode 100644 index a9b4a0e..0000000 --- a/examples/08_embedded_signing_link.py +++ /dev/null @@ -1,25 +0,0 @@ -import signnow_python_sdk - -if __name__ == '__main__': - signnow_python_sdk.Config(client_id="CLIENT_ID", - client_secret="CLIENT_SECRET", - environment="production") - - # Enter your own credentials - username = "USER_NAME" - password = "USER_PASSWORD" - document_id = "DOCUMENT_ID" - - # Create access_token for the user - access_token = signnow_python_sdk.OAuth2.request_token(username, password, '*') - - # Create the signing links for a document. - signing_link = signnow_python_sdk.Link.create(access_token['access_token'], document_id) - - # Embed signing link to your website or share directly - ''' -