Skip to content

Commit 23afa57

Browse files
initial commit with nocodb python client library
0 parents  commit 23afa57

File tree

9 files changed

+354
-0
lines changed

9 files changed

+354
-0
lines changed

LICENSE

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2022 Samuel López Saura
2+
3+
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:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
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.

README.md

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# NocoDB Python Client
2+
3+
NocoDB is a great Airtable alternative. This client allows python developers
4+
to use NocoDB API in a simple way.
5+
6+
## Usage
7+
8+
### Client configuration
9+
```python
10+
from nocodb.nocodb import NocoDBProject, APIToken, JWTAuthToken
11+
from nocodb.filters import InFilter, EqFilter
12+
from nocodb.infra.requests_client import NocoDBRequestsClient
13+
14+
15+
# Usage with API Token
16+
client = NocoDBRequestsClient(
17+
# Your API Token retrieved from NocoDB conf
18+
APIToken("YOUR-API-TOKEN"),
19+
# Your nocodb root path
20+
"http://localhost:8080"
21+
)
22+
23+
# Usage with JWT Token
24+
client = NocoDBRequestsClient(
25+
# Your API Token retrieved from NocoDB conf
26+
JWTAuthToken("your.jwt.token"),
27+
# Your nocodb root path
28+
"http://localhost:8080"
29+
)
30+
```
31+
32+
### Project selection
33+
```python
34+
# Be very carefull with org, project_name and table names
35+
# weird errors from nocodb can arrive if they are wrong
36+
# example: id is not defined...
37+
# probably they will fix that in a future release.
38+
project = NocoDBProject(
39+
"noco", # org name. noco by default
40+
"myproject" # project name. Case sensitive!!
41+
)
42+
43+
```
44+
45+
### Table rows operations
46+
```python
47+
table_name = "tablename"
48+
49+
# Retrieve a page of rows from a table
50+
table_rows = client.table_row_list(project, table_name)
51+
52+
# Retrieve the first 10000 rows
53+
table_rows = client.table_row_list(project, table_name, params={'limit': 10000})
54+
55+
# Skip 100 rows
56+
table_rows = client.table_row_list(project, table_name, params={'offset': 100})
57+
58+
# Filter the query
59+
# Currently only one filter at a time is allowed. I don't know how to join
60+
# multiple conditions in nocodb dsl. If you know how please let me know :).
61+
table_rows = client.table_row_list(project, table_name, InFilter("name", "sam"))
62+
table_rows = client.table_row_list(project, table_name, filter_obj=EqFilter("Id", 100))
63+
64+
# Retrieve a single row
65+
row_id = 10
66+
row = client.table_row_detail(project, table_name, row_id)
67+
68+
# Create a new row
69+
row_info = {
70+
"name": "my thoughts",
71+
"content": "i'm going to buy samuel a beer because i love this module",
72+
"mood": ":)"
73+
}
74+
client.table_row_create(project, table_name, row_info)
75+
76+
# Update a row
77+
row_id = 2
78+
row_info = {
79+
"content": "i'm going to buy samuel a new car because i love this module",
80+
}
81+
client.table_row_update(project, table_name, row_id, row_info)
82+
83+
# Delete a row (only if you've already bought me a beer)
84+
client.table_row_delete(project, table_name, row_id)
85+
```
86+
87+
## Author notes
88+
89+
I created this package to bootstrap some personal projects and I hope it
90+
will help other developers from the python community. It's not completed but
91+
it has what I needed: A full CRUD with some filters.
92+
93+
Feel free to add new capabilities by creating a new MR.
94+
95+
## Contributors
96+
97+
- Samuel López Saura

nocodb/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.0.1"

nocodb/api.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from enum import Enum
2+
from .nocodb import NocoDBProject
3+
4+
5+
class NocoDBAPIUris(Enum):
6+
V1_DB_DATA_PREFIX = "api/v1/db/data"
7+
8+
9+
class NocoDBAPI:
10+
def __init__(self, base_uri: str):
11+
self.__base_data_uri = (
12+
f"{base_uri}/{NocoDBAPIUris.V1_DB_DATA_PREFIX.value}"
13+
)
14+
15+
def get_table_uri(self, project: NocoDBProject, table: str) -> str:
16+
return "/".join(
17+
(
18+
self.__base_data_uri,
19+
project.org_name,
20+
project.project_name,
21+
table,
22+
)
23+
)
24+
25+
def get_row_detail_uri(
26+
self, project: NocoDBProject, table: str, row_id: int
27+
):
28+
return "/".join(
29+
(
30+
self.__base_data_uri,
31+
project.org_name,
32+
project.project_name,
33+
table,
34+
str(row_id),
35+
)
36+
)
37+
38+
def get_nested_relations_rows_list_uri(
39+
self,
40+
project: NocoDBProject,
41+
table: str,
42+
relation_type: str,
43+
row_id: int,
44+
column_name: str,
45+
) -> str:
46+
return "/".join(
47+
(
48+
self.__base_data_uri,
49+
project.org_name,
50+
project.project_name,
51+
table,
52+
str(row_id),
53+
relation_type,
54+
column_name,
55+
)
56+
)

nocodb/filters.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from .nocodb import WhereFilter
2+
3+
4+
class InFilter(WhereFilter):
5+
def __init__(self, column_name: str, value: str):
6+
self.__column_name = column_name
7+
self.__value = value
8+
9+
def get_where(self) -> str:
10+
return f"({self.__column_name},like,%{self.__value}%)"
11+
12+
13+
class EqFilter(WhereFilter):
14+
def __init__(self, column_name: str, value: str):
15+
self.__column_name = column_name
16+
self.__value = value
17+
18+
def get_where(self) -> str:
19+
return f"({self.__column_name},eq,{self.__value})"

nocodb/infra/__init__.py

Whitespace-only changes.

nocodb/infra/requests_client.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from typing import Optional
2+
from ..nocodb import (
3+
NocoDBClient,
4+
NocoDBProject,
5+
AuthToken,
6+
WhereFilter,
7+
)
8+
from ..api import NocoDBAPI
9+
from ..utils import get_query_params
10+
11+
import requests
12+
13+
14+
class NocoDBRequestsClient(NocoDBClient):
15+
def __init__(self, auth_token: AuthToken, base_uri: str):
16+
self.__session = requests.Session()
17+
self.__session.headers.update(
18+
auth_token.get_header(),
19+
)
20+
self.__session.headers.update({"Content-Type": "application/json"})
21+
self.__api_info = NocoDBAPI(base_uri)
22+
23+
def table_row_list(
24+
self,
25+
project: NocoDBProject,
26+
table: str,
27+
filter_obj: Optional[WhereFilter] = None,
28+
params: Optional[dict] = None,
29+
) -> dict:
30+
31+
response = self.__session.get(
32+
self.__api_info.get_table_uri(project, table),
33+
params=get_query_params(filter_obj, params),
34+
)
35+
return response.json()
36+
37+
def table_row_create(
38+
self, project: NocoDBProject, table: str, body: dict
39+
) -> dict:
40+
return self.__session.post(
41+
self.__api_info.get_table_uri(project, table), json=body
42+
).json()
43+
44+
def table_row_detail(
45+
self, project: NocoDBProject, table: str, row_id: int
46+
) -> dict:
47+
return self.__session.get(
48+
self.__api_info.get_row_detail_uri(project, table, row_id),
49+
).json()
50+
51+
def table_row_update(
52+
self, project: NocoDBProject, table: str, row_id: int, body: dict
53+
) -> dict:
54+
return self.__session.patch(
55+
self.__api_info.get_row_detail_uri(project, table, row_id),
56+
json=body,
57+
).json()
58+
59+
def table_row_delete(
60+
self, project: NocoDBProject, table: str, row_id: int
61+
) -> int:
62+
return self.__session.delete(
63+
self.__api_info.get_row_detail_uri(project, table, row_id),
64+
).json()
65+
66+
def table_row_nested_relations_list(
67+
self,
68+
project: NocoDBProject,
69+
table: str,
70+
relation_type: str,
71+
row_id: int,
72+
column_name: str,
73+
) -> dict:
74+
return self.__session.get(
75+
self.__api_info.get_nested_relations_rows_list_uri(
76+
project, table, relation_type, row_id, column_name
77+
)
78+
).json()

nocodb/nocodb.py

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
"""
5+
License MIT
6+
7+
Copyright 2022 Samuel López Saura
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy of
10+
this software and associated documentation files (the "Software"), to deal in
11+
the Software without restriction, including without limitation the rights to
12+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13+
of the Software, and to permit persons to whom the Software is furnished to do
14+
so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in all
17+
copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25+
SOFTWARE.
26+
"""
27+
28+
29+
class AuthToken(ABC):
30+
@abstractmethod
31+
def get_header(self) -> dict:
32+
pass
33+
34+
35+
class APIToken:
36+
def __init__(self, token: str):
37+
self.__token = token
38+
39+
def get_header(self) -> dict:
40+
return {"xc-token": self.__token}
41+
42+
43+
class JWTAuthToken:
44+
def __init__(self, token: str):
45+
self.__token = token
46+
47+
def get_header(self) -> dict:
48+
return {"xc-auth": self.__token}
49+
50+
51+
class WhereFilter(ABC):
52+
@abstractmethod
53+
def get_where(self) -> str:
54+
pass
55+
56+
57+
"""This could be great but actually I don't know how to join filters in the
58+
NocoDB DSL. I event don't know if this is possible through the current API.
59+
I hope they add docs about it soon.
60+
61+
class NocoDBWhere:
62+
63+
def __init__(self):
64+
self.__filter_array: List[WhereFilter] = []
65+
66+
def add_filter(self, where_filter: WhereFilter) -> NocoDBWhere:
67+
self.__filter_array.append(
68+
where_filter
69+
)
70+
return self
71+
72+
def get_where(self) -> str:
73+
return '&'.join([filter_.get_where() for filter_ in self.__filter_array])
74+
75+
def __str__(self):
76+
return f'Where: "{self.get_where()}"'
77+
"""
78+
79+
80+
class NocoDBProject:
81+
def __init__(self, org_name: str, project_name: str):
82+
self.project_name = project_name
83+
self.org_name = org_name
84+
85+
86+
class NocoDBClient:
87+
@abstractmethod
88+
def table_row_list(
89+
self, project: NocoDBProject, table: str, filter_obj=None, params=None
90+
) -> dict:
91+
pass

nocodb/utils.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def get_query_params(filter_obj, params) -> dict:
2+
query_params = params or {}
3+
if filter_obj:
4+
query_params["where"] = filter_obj.get_where()
5+
return query_params

0 commit comments

Comments
 (0)