Skip to content

Adds the ability to upload local files and associate them with an attachment field. #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ client.table_row_update(project, table_name, row_id, row_info)

# Delete a row (only if you've already bought me a beer)
client.table_row_delete(project, table_name, row_id)

# Upload a file to an attachment field
image_path = "path/to/new_car.png"
image_column = "images"
client.upload_file(project, table, row_id, image_column, image_path)
```

### Available filters
Expand Down
20 changes: 20 additions & 0 deletions nocodb/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
class NocoDBAPIUris(Enum):
V1_DB_DATA_PREFIX = "api/v1/db/data/"
V1_DB_META_PREFIX = "api/v1/db/meta/"
V1_DB_STORAGE_PREFIX = "api/v1/db/storage/"


class NocoDBAPI:
def __init__(self, base_uri: str):
self.__base_data_uri = urljoin(base_uri + "/", NocoDBAPIUris.V1_DB_DATA_PREFIX.value)
self.__base_meta_uri = urljoin(base_uri + "/", NocoDBAPIUris.V1_DB_META_PREFIX.value)
self.__base_storage_uri = urljoin(base_uri + "/", NocoDBAPIUris.V1_DB_STORAGE_PREFIX.value)

def get_table_uri(self, project: NocoDBProject, table: str) -> str:
return urljoin(self.__base_data_uri, "/".join(
Expand Down Expand Up @@ -84,6 +86,24 @@ def get_project_tables_uri(
"tables"
)
))

def get_storage_upload_uri(
self,
) -> str:
return urljoin(self.__base_storage_uri, "upload")

def get_storage_upload_path(
self, project: NocoDBProject, table: str, column_id: str,
) -> str:
"""This Path/URL is used in the request body for uploading attachments."""
return urljoin(f"{project.org_name}/", "/".join(
[
project.project_name,
table,
column_id,
]
))


def get_table_meta_uri(
self, tableId: str, operation: str = None,
Expand Down
41 changes: 39 additions & 2 deletions nocodb/infra/requests_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional

from ..nocodb import (
NocoDBClient,
NocoDBProject,
Expand Down Expand Up @@ -57,10 +58,10 @@ def table_row_create(self, project: NocoDBProject, table: str, body: dict) -> di
"POST", self.__api_info.get_table_uri(project, table), json=body
).json()

def table_row_detail(self, project: NocoDBProject, table: str, row_id: int) -> dict:
def table_row_detail(self, project: NocoDBProject, table_id: str, row_id: int) -> dict:
return self._request(
"GET",
self.__api_info.get_row_detail_uri(project, table, row_id),
self.__api_info.get_row_detail_uri(project, table_id, row_id),
).json()

def table_row_update(
Expand Down Expand Up @@ -210,3 +211,39 @@ def table_column_set_primary(
"POST",
url=self.__api_info.get_column_uri(columnId, "primary"),
).json()

def upload_file(
self, project: NocoDBProject, table_id: str, row_id: int, column_name: str, path: str
) -> dict:
"""Upload a file to an Attachment field."""
content_type = self.__session.headers.get("Content-Type")
self.__session.headers.pop("Content-Type")

# Upload the file to NocoDB's storage.
upload_result = self._request(
"POST",
url=self.__api_info.get_storage_upload_uri(),
data={"path": self.__api_info.get_storage_upload_path(project, table_id, column_name)},
files={"file": open(path, "rb")}
).json()
if len(upload_result) < 1:
raise ValueError("result of storage upload call doesn't contain the file info")
file_data = upload_result[0]

# Link the uploaded file to the given attachment file.
result = self.table_row_update(
project,
table_id,
row_id,
{
column_name: [{
"path": file_data["path"],
"title": file_data["title"],
"size": file_data["size"],
}]
}
)

self.__session.headers.update({"Content-Type": str(content_type)})
return result

11 changes: 9 additions & 2 deletions nocodb/nocodb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional

"""
Expand Down Expand Up @@ -32,15 +33,15 @@ def get_header(self) -> dict:
pass


class APIToken:
class APIToken(AuthToken):
def __init__(self, token: str):
self.__token = token

def get_header(self) -> dict:
return {"xc-token": self.__token}


class JWTAuthToken:
class JWTAuthToken(AuthToken):
def __init__(self, token: str):
self.__token = token

Expand Down Expand Up @@ -173,3 +174,9 @@ def table_column_set_primary(
self, columnId: str,
) -> dict:
pass

@abstractmethod
def upload_file(
self, project: NocoDBProject, table: str, row_id: int, column_id: str, file: Path
):
pass