Skip to content

Commit a09a341

Browse files
committed
feat: Initial
0 parents  commit a09a341

File tree

17 files changed

+1470
-0
lines changed

17 files changed

+1470
-0
lines changed

.dockerignore

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
MANIFEST
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a example
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.nox/
42+
.coverage
43+
.coverage.*
44+
.cache
45+
nosetests.xml
46+
coverage.xml
47+
*.cover
48+
*.py,cover
49+
.hypothesis/
50+
.pytest_cache/
51+
52+
# Translations
53+
*.mo
54+
*.pot
55+
56+
# Django stuff:
57+
*.log
58+
local_settings.py
59+
db.sqlite3
60+
db.sqlite3-journal
61+
62+
# Flask stuff:
63+
instance/
64+
.webassets-cache
65+
66+
# Scrapy stuff:
67+
.scrapy
68+
69+
# Sphinx documentation
70+
docs/_build/
71+
72+
# PyBuilder
73+
target/
74+
75+
# pyenv
76+
.python-version
77+
78+
# celery beat schedule file
79+
celerybeat-schedule
80+
81+
# SageMath parsed files
82+
*.sage.py
83+
84+
# Environments
85+
.env
86+
.venv
87+
env/
88+
venv/
89+
ENV/
90+
91+
# Spyder project settings
92+
.spyderproject
93+
.spyproject
94+
95+
# Rope project settings
96+
.ropeproject
97+
98+
# mkdocs documentation
99+
/site
100+
101+
# mypy
102+
.mypy_cache/
103+
.dmypy.json
104+
dmypy.json
105+
106+
# Pyre type checker
107+
.pyre/
108+
109+
# profiling data
110+
.prof

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
venv
2+
__pycache__

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Changelog

Dockerfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Start from a base image
2+
FROM python:3.11-slim
3+
4+
# (Optional) Set a working directory
5+
WORKDIR /app
6+
7+
# Copy requirements.txt and install the Python dependencies
8+
COPY pyproject.toml .
9+
COPY poetry.lock .
10+
RUN pip3 install --no-cache-dir poetry
11+
RUN poetry install
12+
13+
# Copy the rest of the code
14+
COPY plugin plugin
15+
COPY main.py .
16+
17+
# (Optional) Expose any ports your app uses
18+
EXPOSE 7777
19+
20+
ENTRYPOINT ["python3", "main.py"]
21+
22+
# Specify the command to run when the container starts
23+
CMD ["serve", "--address", "[::]:7777", "--log-format", "json", "--log-level", "info"]

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
test:
2+
pytest .
3+
4+
fmt:
5+
black .
6+
7+
fmt-check:
8+
black --check .

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Python Source Plugin Template
2+
This repo contains everything you need to get started with building a new plugin.
3+
To get started all you need to do is change a few names, define some tables, and write an API Client to populate the tables.
4+
5+
## Key files & classes
6+
- plugin/tables/items.py
7+
- Items - A boilerplate table definition
8+
- ItemResolver - A boilerplate table resolver
9+
- plugin/example/client.py
10+
- ExampleClient - A boilerplate API Client
11+
- plugin/client/client.py
12+
- Spec - Defines the CloudQuery Config
13+
- Client (uses: ExampleClient) - Wraps your API Client
14+
- plugin/plugin.py
15+
- ExamplePlugin - The plugin registration / how CloudQuery knows what tables your plugin exposes.
16+
17+
18+
19+
## Getting started
20+
21+
### Defining your tables
22+
The first thing you need to do is identify the tables you want to create with your plugin.
23+
Conventionally, CloudQuery plugins have a direct relationship between tables and API responses.
24+
25+
For example:
26+
If you had an API endpoint https://api.example.com/items/{num} and for each value of `num` it provided an object
27+
```json
28+
{
29+
"num": {{num}},
30+
"date": "2023-10-12",
31+
"title": "A simple example"
32+
}
33+
```
34+
Then you would design the table class as
35+
```python
36+
class Items(Table):
37+
def __init__(self) -> None:
38+
super().__init__(
39+
name="item",
40+
title="Item",
41+
columns=[
42+
Column("num", pa.uint64(), primary_key=True),
43+
Column("date", pa.date64()),
44+
Column("title", pa.string()),
45+
],
46+
)
47+
...
48+
```
49+
50+
Creating one table for each endpoint that you want to capture.
51+
52+
### API Client
53+
Next you'll need to define how the tables are retrieved, it's recommended to implement this as a generator, as per the example in `plugin/example/client.py`.
54+
55+
### Spec
56+
Having written your API Client you will have, identified the authentication and/or operational variables needed.
57+
Adding these to the CloudQuery config spec can be done by editing the `Spec` `dataclass` using standard python, and adding validation where needed.
58+
59+
### Plugin
60+
Finally, you need to edit the `plugin.py` file to set the plugin name and version, and add the `Tables` to the `get_tables` function.
61+
62+
63+
## Links
64+
65+
- [Architecture](https://www.cloudquery.io/docs/developers/architecture)
66+
- [Concepts](https://www.cloudquery.io/docs/developers/creating-new-plugin/python-source)

main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import sys
2+
from cloudquery.sdk import serve
3+
4+
from plugin import ExamplePlugin
5+
6+
7+
def main():
8+
p = ExamplePlugin()
9+
serve.PluginCommand(p).run(sys.argv[1:])
10+
11+
12+
if __name__ == "__main__":
13+
main()

plugin/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .plugin import ExamplePlugin

plugin/client/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .client import Client, Spec

plugin/client/client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from dataclasses import dataclass, field
2+
from cloudquery.sdk.scheduler import Client as ClientABC
3+
4+
from plugin.example.client import ExampleClient
5+
6+
DEFAULT_CONCURRENCY = 100
7+
DEFAULT_QUEUE_SIZE = 10000
8+
9+
10+
@dataclass
11+
class Spec:
12+
access_token: str
13+
base_url: str = field(default="https://api.example.com")
14+
concurrency: int = field(default=DEFAULT_CONCURRENCY)
15+
queue_size: int = field(default=DEFAULT_QUEUE_SIZE)
16+
17+
def validate(self):
18+
pass
19+
# if self.access_token is None:
20+
# raise Exception("access_token must be provided")
21+
22+
23+
class Client(ClientABC):
24+
def __init__(self, spec: Spec) -> None:
25+
self._spec = spec
26+
self._client = ExampleClient(spec.access_token, spec.base_url)
27+
28+
def id(self):
29+
return "example"
30+
31+
@property
32+
def client(self) -> ExampleClient:
33+
return self._client

0 commit comments

Comments
 (0)