From ca0d967f8d78197560d433e1953e51c962aef745 Mon Sep 17 00:00:00 2001 From: Pat Nadolny Date: Fri, 15 Mar 2024 10:23:31 -0400 Subject: [PATCH] init from cookiecutter --- .github/dependabot.yml | 26 +++++ .github/workflows/test.yml | 32 ++++++ .gitignore | 136 ++++++++++++++++++++++++++ .pre-commit-config.yaml | 38 ++++++++ .secrets/.gitignore | 10 ++ .vscode/launch.json | 20 ++++ README.md | 132 ++++++++++++++++++++++++- meltano.yml | 30 ++++++ output/.gitignore | 4 + pyproject.toml | 68 +++++++++++++ tap_gohighlevel/__init__.py | 1 + tap_gohighlevel/__main__.py | 7 ++ tap_gohighlevel/auth.py | 44 +++++++++ tap_gohighlevel/client.py | 145 ++++++++++++++++++++++++++++ tap_gohighlevel/schemas/__init__.py | 1 + tap_gohighlevel/streams.py | 72 ++++++++++++++ tap_gohighlevel/tap.py | 58 +++++++++++ tests/__init__.py | 1 + tests/test_core.py | 22 +++++ tox.ini | 19 ++++ 20 files changed, 865 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .secrets/.gitignore create mode 100644 .vscode/launch.json create mode 100644 meltano.yml create mode 100644 output/.gitignore create mode 100644 pyproject.toml create mode 100644 tap_gohighlevel/__init__.py create mode 100644 tap_gohighlevel/__main__.py create mode 100644 tap_gohighlevel/auth.py create mode 100644 tap_gohighlevel/client.py create mode 100644 tap_gohighlevel/schemas/__init__.py create mode 100644 tap_gohighlevel/streams.py create mode 100644 tap_gohighlevel/tap.py create mode 100644 tests/__init__.py create mode 100644 tests/test_core.py create mode 100644 tox.ini diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..933e6b1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: pip + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: "chore(deps): " + prefix-development: "chore(deps-dev): " + - package-ecosystem: pip + directory: "/.github/workflows" + schedule: + interval: daily + commit-message: + prefix: "ci: " + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci: " diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ab37dca --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +### A CI workflow template that runs linting and python testing +### TODO: Modify as needed or as desired. + +name: Test tap-gohighlevel + +on: [push] + +jobs: + pytest: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install Poetry + run: | + pip install poetry + - name: Install dependencies + run: | + poetry env use ${{ matrix.python-version }} + poetry install + - name: Test with pytest + run: | + poetry run pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..475019c --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# Secrets and internal config files +**/.secrets/* + +# Ignore meltano internal cache and sqlite systemdb + +.meltano/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# 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 + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# 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 +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__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/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..567bcaf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +ci: + autofix_prs: true + autoupdate_schedule: weekly + autoupdate_commit_msg: 'chore: pre-commit autoupdate' + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-json + exclude: | + (?x)^( + \.vscode/.*\.json + )$ + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.0 + hooks: + - id: check-dependabot + - id: check-github-workflows + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.1 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --show-fixes] + - id: ruff-format + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + additional_dependencies: + - types-requests diff --git a/.secrets/.gitignore b/.secrets/.gitignore new file mode 100644 index 0000000..33c6acd --- /dev/null +++ b/.secrets/.gitignore @@ -0,0 +1,10 @@ +# IMPORTANT! This folder is hidden from git - if you need to store config files or other secrets, +# make sure those are never staged for commit into your git repo. You can store them here or another +# secure location. +# +# Note: This may be redundant with the global .gitignore for, and is provided +# for redundancy. If the `.secrets` folder is not needed, you may delete it +# from the project. + +* +!.gitignore diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7370103 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "tap-gohighlevel", + "type": "python", + "request": "launch", + "cwd": "${workspaceFolder}", + "program": "tap_gohighlevel", + "justMyCode": false, + "args": [ + "--config", + ".secrets/config.json", + ], + }, + ] +} diff --git a/README.md b/README.md index 9156ad9..53f09d9 100644 --- a/README.md +++ b/README.md @@ -1 +1,131 @@ -# tap-gohighlevel \ No newline at end of file +# tap-gohighlevel + +`tap-gohighlevel` is a Singer tap for GoHighLevel. + +Built with the [Meltano Tap SDK](https://sdk.meltano.com) for Singer Taps. + + + +## Configuration + +### Accepted Config Options + + + +A full list of supported settings and capabilities for this +tap is available by running: + +```bash +tap-gohighlevel --about +``` + +### Configure using environment variables + +This Singer tap will automatically import any environment variables within the working directory's +`.env` if the `--config=ENV` is provided, such that config values will be considered if a matching +environment variable is set either in the terminal context or in the `.env` file. + +### Source Authentication and Authorization + + + +## Usage + +You can easily run `tap-gohighlevel` by itself or in a pipeline using [Meltano](https://meltano.com/). + +### Executing the Tap Directly + +```bash +tap-gohighlevel --version +tap-gohighlevel --help +tap-gohighlevel --config CONFIG --discover > ./catalog.json +``` + +## Developer Resources + +Follow these instructions to contribute to this project. + +### Initialize your Development Environment + +```bash +pipx install poetry +poetry install +``` + +### Create and Run Tests + +Create tests within the `tests` subfolder and + then run: + +```bash +poetry run pytest +``` + +You can also test the `tap-gohighlevel` CLI interface directly using `poetry run`: + +```bash +poetry run tap-gohighlevel --help +``` + +### Testing with [Meltano](https://www.meltano.com) + +_**Note:** This tap will work in any Singer environment and does not require Meltano. +Examples here are for convenience and to streamline end-to-end orchestration scenarios._ + + + +Next, install Meltano (if you haven't already) and any needed plugins: + +```bash +# Install meltano +pipx install meltano +# Initialize meltano within this directory +cd tap-gohighlevel +meltano install +``` + +Now you can test and orchestrate using Meltano: + +```bash +# Test invocation: +meltano invoke tap-gohighlevel --version +# OR run a test `elt` pipeline: +meltano elt tap-gohighlevel target-jsonl +``` + +### SDK Dev Guide + +See the [dev guide](https://sdk.meltano.com/en/latest/dev_guide.html) for more instructions on how to use the SDK to +develop your own taps and targets. diff --git a/meltano.yml b/meltano.yml new file mode 100644 index 0000000..f99576a --- /dev/null +++ b/meltano.yml @@ -0,0 +1,30 @@ +version: 1 +send_anonymous_usage_stats: true +project_id: "tap-gohighlevel" +default_environment: test +environments: +- name: test +plugins: + extractors: + - name: "tap-gohighlevel" + namespace: "tap_gohighlevel" + pip_url: -e . + capabilities: + - state + - catalog + - discover + - about + - stream-maps + config: + start_date: '2010-01-01T00:00:00Z' + settings: + # TODO: To configure using Meltano, declare settings and their types here: + - name: username + - name: password + kind: password + - name: start_date + value: '2010-01-01T00:00:00Z' + loaders: + - name: target-jsonl + variant: andyh1203 + pip_url: target-jsonl diff --git a/output/.gitignore b/output/.gitignore new file mode 100644 index 0000000..80ff9d2 --- /dev/null +++ b/output/.gitignore @@ -0,0 +1,4 @@ +# This directory is used as a target by target-jsonl, so ignore all files + +* +!.gitignore diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6985935 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,68 @@ +[tool.poetry] +name = "tap-gohighlevel" +version = "0.0.1" +description = "`tap-gohighlevel` is a Singer tap for GoHighLevel, built with the Meltano Singer SDK." +readme = "README.md" +authors = ["Pat Nadolny "] +keywords = [ + "ELT", + "GoHighLevel", +] +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +license = "Apache-2.0" + +[tool.poetry.dependencies] +python = ">=3.8" +importlib-resources = { version = "==6.1.*", python = "<3.9" } +singer-sdk = { version="~=0.36.1" } +fs-s3fs = { version = "~=1.1.1", optional = true } +requests = "~=2.31.0" + +[tool.poetry.group.dev.dependencies] +pytest = ">=7.4.0" +singer-sdk = { version="~=0.36.1", extras = ["testing"] } + +[tool.poetry.extras] +s3 = ["fs-s3fs"] + +[tool.mypy] +python_version = "3.12" +warn_unused_configs = true + +[tool.ruff] +src = ["tap_gohighlevel"] +target-version = "py38" + +[tool.ruff.lint] +ignore = [ + "ANN101", # missing-type-self + "ANN102", # missing-type-cls + "COM812", # missing-trailing-comma + "ISC001", # single-line-implicit-string-concatenation +] +select = ["ALL"] + +[tool.ruff.lint.flake8-annotations] +allow-star-arg-any = true + +[tool.ruff.lint.isort] +known-first-party = ["tap_gohighlevel"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[build-system] +requires = ["poetry-core==1.9.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +# CLI declaration +tap-gohighlevel = 'tap_gohighlevel.tap:TapGoHighLevel.cli' diff --git a/tap_gohighlevel/__init__.py b/tap_gohighlevel/__init__.py new file mode 100644 index 0000000..de57dc1 --- /dev/null +++ b/tap_gohighlevel/__init__.py @@ -0,0 +1 @@ +"""Tap for GoHighLevel.""" diff --git a/tap_gohighlevel/__main__.py b/tap_gohighlevel/__main__.py new file mode 100644 index 0000000..cb0b8aa --- /dev/null +++ b/tap_gohighlevel/__main__.py @@ -0,0 +1,7 @@ +"""GoHighLevel entry point.""" + +from __future__ import annotations + +from tap_gohighlevel.tap import TapGoHighLevel + +TapGoHighLevel.cli() diff --git a/tap_gohighlevel/auth.py b/tap_gohighlevel/auth.py new file mode 100644 index 0000000..c2e1506 --- /dev/null +++ b/tap_gohighlevel/auth.py @@ -0,0 +1,44 @@ +"""GoHighLevel Authentication.""" + +from __future__ import annotations + +from singer_sdk.authenticators import OAuthAuthenticator, SingletonMeta + + +# The SingletonMeta metaclass makes your streams reuse the same authenticator instance. +# If this behaviour interferes with your use-case, you can remove the metaclass. +class GoHighLevelAuthenticator(OAuthAuthenticator, metaclass=SingletonMeta): + """Authenticator class for GoHighLevel.""" + + @property + def oauth_request_body(self) -> dict: + """Define the OAuth request body for the AutomaticTestTap API. + + Returns: + A dict with the request body + """ + # TODO: Define the request body needed for the API. + return { + "resource": "https://analysis.windows.net/powerbi/api", + "scope": self.oauth_scopes, + "client_id": self.config["client_id"], + "username": self.config["username"], + "password": self.config["password"], + "grant_type": "password", + } + + @classmethod + def create_for_stream(cls, stream) -> GoHighLevelAuthenticator: # noqa: ANN001 + """Instantiate an authenticator for a specific Singer stream. + + Args: + stream: The Singer stream instance. + + Returns: + A new authenticator. + """ + return cls( + stream=stream, + auth_endpoint="TODO: OAuth Endpoint URL", + oauth_scopes="TODO: OAuth Scopes", + ) diff --git a/tap_gohighlevel/client.py b/tap_gohighlevel/client.py new file mode 100644 index 0000000..b46f116 --- /dev/null +++ b/tap_gohighlevel/client.py @@ -0,0 +1,145 @@ +"""REST client handling, including GoHighLevelStream base class.""" + +from __future__ import annotations + +import sys +from functools import cached_property +from typing import Any, Callable, Iterable + +import requests +from singer_sdk.helpers.jsonpath import extract_jsonpath +from singer_sdk.pagination import BaseAPIPaginator # noqa: TCH002 +from singer_sdk.streams import RESTStream + +from tap_gohighlevel.auth import GoHighLevelAuthenticator + +if sys.version_info >= (3, 9): + import importlib.resources as importlib_resources +else: + import importlib_resources + +_Auth = Callable[[requests.PreparedRequest], requests.PreparedRequest] + +# TODO: Delete this is if not using json files for schema definition +SCHEMAS_DIR = importlib_resources.files(__package__) / "schemas" + + +class GoHighLevelStream(RESTStream): + """GoHighLevel stream class.""" + + @property + def url_base(self) -> str: + """Return the API URL root, configurable via tap settings.""" + # TODO: hardcode a value here, or retrieve it from self.config + return "https://api.mysample.com" + + records_jsonpath = "$[*]" # Or override `parse_response`. + + # Set this value or override `get_new_paginator`. + next_page_token_jsonpath = "$.next_page" # noqa: S105 + + @cached_property + def authenticator(self) -> _Auth: + """Return a new authenticator object. + + Returns: + An authenticator instance. + """ + return GoHighLevelAuthenticator.create_for_stream(self) + + @property + def http_headers(self) -> dict: + """Return the http headers needed. + + Returns: + A dictionary of HTTP headers. + """ + headers = {} + if "user_agent" in self.config: + headers["User-Agent"] = self.config.get("user_agent") + return headers + + def get_new_paginator(self) -> BaseAPIPaginator: + """Create a new pagination helper instance. + + If the source API can make use of the `next_page_token_jsonpath` + attribute, or it contains a `X-Next-Page` header in the response + then you can remove this method. + + If you need custom pagination that uses page numbers, "next" links, or + other approaches, please read the guide: https://sdk.meltano.com/en/v0.25.0/guides/pagination-classes.html. + + Returns: + A pagination helper instance. + """ + return super().get_new_paginator() + + def get_url_params( + self, + context: dict | None, # noqa: ARG002 + next_page_token: Any | None, # noqa: ANN401 + ) -> dict[str, Any]: + """Return a dictionary of values to be used in URL parameterization. + + Args: + context: The stream context. + next_page_token: The next page index or value. + + Returns: + A dictionary of URL query parameters. + """ + params: dict = {} + if next_page_token: + params["page"] = next_page_token + if self.replication_key: + params["sort"] = "asc" + params["order_by"] = self.replication_key + return params + + def prepare_request_payload( + self, + context: dict | None, # noqa: ARG002 + next_page_token: Any | None, # noqa: ARG002, ANN401 + ) -> dict | None: + """Prepare the data payload for the REST API request. + + By default, no payload will be sent (return None). + + Args: + context: The stream context. + next_page_token: The next page index or value. + + Returns: + A dictionary with the JSON body for a POST requests. + """ + # TODO: Delete this method if no payload is required. (Most REST APIs.) + return None + + def parse_response(self, response: requests.Response) -> Iterable[dict]: + """Parse the response and return an iterator of result records. + + Args: + response: The HTTP ``requests.Response`` object. + + Yields: + Each record from the source. + """ + # TODO: Parse response body and return a set of records. + yield from extract_jsonpath(self.records_jsonpath, input=response.json()) + + def post_process( + self, + row: dict, + context: dict | None = None, # noqa: ARG002 + ) -> dict | None: + """As needed, append or transform raw data to match expected structure. + + Args: + row: An individual record from the stream. + context: The stream context. + + Returns: + The updated record dictionary, or ``None`` to skip the record. + """ + # TODO: Delete this method if not needed. + return row diff --git a/tap_gohighlevel/schemas/__init__.py b/tap_gohighlevel/schemas/__init__.py new file mode 100644 index 0000000..06c0a19 --- /dev/null +++ b/tap_gohighlevel/schemas/__init__.py @@ -0,0 +1 @@ +"""JSON schema files for the REST API.""" diff --git a/tap_gohighlevel/streams.py b/tap_gohighlevel/streams.py new file mode 100644 index 0000000..64d7b0d --- /dev/null +++ b/tap_gohighlevel/streams.py @@ -0,0 +1,72 @@ +"""Stream type classes for tap-gohighlevel.""" + +from __future__ import annotations + +import sys +import typing as t + +from singer_sdk import typing as th # JSON Schema typing helpers + +from tap_gohighlevel.client import GoHighLevelStream + +if sys.version_info >= (3, 9): + import importlib.resources as importlib_resources +else: + import importlib_resources + + +# TODO: Delete this is if not using json files for schema definition +SCHEMAS_DIR = importlib_resources.files(__package__) / "schemas" +# TODO: - Override `UsersStream` and `GroupsStream` with your own stream definition. +# - Copy-paste as many times as needed to create multiple stream types. + + +class UsersStream(GoHighLevelStream): + """Define custom stream.""" + + name = "users" + path = "/users" + primary_keys: t.ClassVar[list[str]] = ["id"] + replication_key = None + # Optionally, you may also use `schema_filepath` in place of `schema`: + # schema_filepath = SCHEMAS_DIR / "users.json" # noqa: ERA001 + schema = th.PropertiesList( + th.Property("name", th.StringType), + th.Property( + "id", + th.StringType, + description="The user's system ID", + ), + th.Property( + "age", + th.IntegerType, + description="The user's age in years", + ), + th.Property( + "email", + th.StringType, + description="The user's email address", + ), + th.Property("street", th.StringType), + th.Property("city", th.StringType), + th.Property( + "state", + th.StringType, + description="State name in ISO 3166-2 format", + ), + th.Property("zip", th.StringType), + ).to_dict() + + +class GroupsStream(GoHighLevelStream): + """Define custom stream.""" + + name = "groups" + path = "/groups" + primary_keys: t.ClassVar[list[str]] = ["id"] + replication_key = "modified" + schema = th.PropertiesList( + th.Property("name", th.StringType), + th.Property("id", th.StringType), + th.Property("modified", th.DateTimeType), + ).to_dict() diff --git a/tap_gohighlevel/tap.py b/tap_gohighlevel/tap.py new file mode 100644 index 0000000..578f5a7 --- /dev/null +++ b/tap_gohighlevel/tap.py @@ -0,0 +1,58 @@ +"""GoHighLevel tap class.""" + +from __future__ import annotations + +from singer_sdk import Tap +from singer_sdk import typing as th # JSON schema typing helpers + +# TODO: Import your custom stream types here: +from tap_gohighlevel import streams + + +class TapGoHighLevel(Tap): + """GoHighLevel tap class.""" + + name = "tap-gohighlevel" + + # TODO: Update this section with the actual config values you expect: + config_jsonschema = th.PropertiesList( + th.Property( + "auth_token", + th.StringType, + required=True, + secret=True, # Flag config as protected. + description="The token to authenticate against the API service", + ), + th.Property( + "project_ids", + th.ArrayType(th.StringType), + required=True, + description="Project IDs to replicate", + ), + th.Property( + "start_date", + th.DateTimeType, + description="The earliest record date to sync", + ), + th.Property( + "api_url", + th.StringType, + default="https://api.mysample.com", + description="The url for the API service", + ), + ).to_dict() + + def discover_streams(self) -> list[streams.GoHighLevelStream]: + """Return a list of discovered streams. + + Returns: + A list of discovered streams. + """ + return [ + streams.GroupsStream(self), + streams.UsersStream(self), + ] + + +if __name__ == "__main__": + TapGoHighLevel.cli() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..f222f7c --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test suite for tap-gohighlevel.""" diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..f8934b2 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,22 @@ +"""Tests standard tap features using the built-in SDK tests library.""" + +import datetime + +from singer_sdk.testing import get_tap_test_class + +from tap_gohighlevel.tap import TapGoHighLevel + +SAMPLE_CONFIG = { + "start_date": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d"), + # TODO: Initialize minimal tap config +} + + +# Run standard built-in tap tests from the SDK: +TestTapGoHighLevel = get_tap_test_class( + tap_class=TapGoHighLevel, + config=SAMPLE_CONFIG, +) + + +# TODO: Create additional tests as appropriate for your tap. diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..6be1c11 --- /dev/null +++ b/tox.ini @@ -0,0 +1,19 @@ +# This file can be used to customize tox tests as well as other test frameworks like flake8 and mypy + +[tox] +envlist = py{38,39,310,311,312} +isolated_build = true + +[testenv] +allowlist_externals = poetry +commands = + poetry install -v + poetry run pytest + +[testenv:pytest] +# Run the python tests. +# To execute, run `tox -e pytest` +envlist = py{38,39,310,311,312} +commands = + poetry install -v + poetry run pytest