Skip to content

Commit e7f81b1

Browse files
authored
Merge pull request #23 from ks6088ts-labs/feature/issue-22_blob-storage-service
add blob storage solution
2 parents 8663579 + ef37052 commit e7f81b1

21 files changed

+730
-6
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ install-deps: ## install dependencies for production
2323

2424
.PHONY: format-check
2525
format-check: ## format check
26-
poetry run black . --check --verbose
26+
poetry run black . --check --extend-exclude client/ --verbose
2727

2828
.PHONY: format
2929
format: ## format code
3030
poetry run isort .
31-
poetry run black . --verbose
31+
poetry run black . --extend-exclude client/ --verbose
3232

3333
.PHONY: fix
3434
fix: format ## apply auto-fixes

azure_storage.env.sample

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AZURE_STORAGE_ACCOUNT_NAME = "<account-name>"
2+
AZURE_STORAGE_SAS_TOKEN = "<sas-token>"
3+
AZURE_STORAGE_BLOB_CONTAINER_NAME = "<blob-container-name>"

backend/fastapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from fastapi.openapi.utils import get_openapi
33

44
from backend.routers import azure_openai as azure_openai_router
5+
from backend.routers import azure_storage as azure_storage_router
56
from backend.routers import document_intelligence as document_intelligence_router
67

78
app = FastAPI(
@@ -10,6 +11,7 @@
1011

1112
app.include_router(azure_openai_router.router)
1213
app.include_router(document_intelligence_router.router)
14+
app.include_router(azure_storage_router.router)
1315

1416

1517
def custom_openapi():

backend/internals/azure_storage.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from logging import getLogger
2+
3+
from azure.storage.blob import BlobServiceClient
4+
5+
from backend.settings import azure_storage as azure_storage_settings
6+
7+
logger = getLogger(__name__)
8+
9+
10+
class BlobStorageClient:
11+
def __init__(self, settings: azure_storage_settings.Settings):
12+
self.settings = settings
13+
14+
def get_blob_service_client(self) -> BlobServiceClient:
15+
return BlobServiceClient(
16+
account_url=f"https://{self.settings.azure_storage_account_name}.blob.core.windows.net",
17+
credential=self.settings.azure_storage_sas_token,
18+
)
19+
20+
def upload_blob_stream(
21+
self,
22+
blob_name: str,
23+
stream: bytes,
24+
):
25+
blob_service_client = self.get_blob_service_client()
26+
blob_client = blob_service_client.get_blob_client(
27+
container=self.settings.azure_storage_blob_container_name,
28+
blob=blob_name,
29+
)
30+
blob_client.upload_blob(stream, overwrite=True)
31+
logger.info(f"Uploaded blob {blob_name} to container {self.settings.azure_storage_blob_container_name}")
32+
33+
def download_blob_stream(
34+
self,
35+
blob_name: str,
36+
) -> bytes:
37+
blob_service_client = self.get_blob_service_client()
38+
blob_client = blob_service_client.get_blob_client(
39+
container=self.settings.azure_storage_blob_container_name,
40+
blob=blob_name,
41+
)
42+
stream = blob_client.download_blob().readall()
43+
logger.info(f"Downloaded blob {blob_name} from container {self.settings.azure_storage_blob_container_name}")
44+
return stream
45+
46+
def delete_blob(
47+
self,
48+
blob_name: str,
49+
):
50+
blob_service_client = self.get_blob_service_client()
51+
blob_client = blob_service_client.get_blob_client(
52+
container=self.settings.azure_storage_blob_container_name,
53+
blob=blob_name,
54+
)
55+
blob_client.delete_blob()
56+
logger.info(f"Deleted blob {blob_name} from container {self.settings.azure_storage_blob_container_name}")
57+
58+
def list_blobs(
59+
self,
60+
) -> list:
61+
blob_service_client = self.get_blob_service_client()
62+
container_client = blob_service_client.get_container_client(self.settings.azure_storage_blob_container_name)
63+
logger.info(f"Listed blobs in container {self.settings.azure_storage_blob_container_name}")
64+
return [blob.name for blob in container_client.list_blobs()]

backend/routers/azure_storage.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from logging import getLogger
2+
3+
from fastapi import APIRouter, UploadFile, status
4+
from fastapi.responses import JSONResponse
5+
6+
from backend.internals import azure_storage
7+
from backend.schemas import azure_storage as azure_storage_schemas
8+
from backend.settings.azure_storage import Settings as AzureStorageSettings
9+
10+
logger = getLogger(__name__)
11+
blob_storage_client = azure_storage.BlobStorageClient(
12+
settings=AzureStorageSettings(),
13+
)
14+
15+
router = APIRouter(
16+
prefix="/azure_storage",
17+
tags=["azure_storage"],
18+
responses={404: {"description": "Not found"}},
19+
)
20+
21+
22+
@router.post(
23+
"/blobs/upload/",
24+
response_model=azure_storage_schemas.BlobUploadResponse,
25+
status_code=200,
26+
)
27+
async def upload_blob(
28+
file: UploadFile,
29+
blob_name: str,
30+
):
31+
try:
32+
content = await file.read()
33+
blob_storage_client.upload_blob_stream(
34+
blob_name=blob_name,
35+
stream=content,
36+
)
37+
except Exception as e:
38+
logger.error(f"Failed to upload blob: {e}")
39+
raise
40+
return azure_storage_schemas.BlobUploadResponse(
41+
blob_name=blob_name,
42+
)
43+
44+
45+
@router.delete(
46+
"/blobs/delete/",
47+
status_code=200,
48+
)
49+
async def delete_blob(
50+
blob_name: str,
51+
):
52+
try:
53+
blob_storage_client.delete_blob(
54+
blob_name=blob_name,
55+
)
56+
except Exception as e:
57+
logger.error(f"Failed to delete blob: {e}")
58+
raise
59+
return JSONResponse(
60+
status_code=status.HTTP_200_OK,
61+
content={"blob_name": blob_name},
62+
)
63+
64+
65+
@router.get(
66+
"/blobs/",
67+
status_code=200,
68+
)
69+
async def list_blobs():
70+
try:
71+
blobs = blob_storage_client.list_blobs()
72+
except Exception as e:
73+
logger.error(f"Failed to upload blob: {e}")
74+
raise
75+
return JSONResponse(
76+
status_code=status.HTTP_200_OK,
77+
content={"blobs": blobs},
78+
)

backend/schemas/azure_storage.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from logging import getLogger
2+
3+
from pydantic import BaseModel
4+
5+
logger = getLogger(__name__)
6+
7+
8+
class BlobUploadResponse(BaseModel):
9+
blob_name: str

backend/settings/azure_storage.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from pydantic_settings import BaseSettings, SettingsConfigDict
2+
3+
4+
class Settings(BaseSettings):
5+
azure_storage_account_name: str = "<account-name>"
6+
azure_storage_sas_token: str = "<sas-token>"
7+
azure_storage_blob_container_name: str = "<blob-container-name>"
8+
9+
model_config = SettingsConfigDict(
10+
env_file="azure_storage.env",
11+
env_file_encoding="utf-8",
12+
)

client/api_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
if TYPE_CHECKING:
1717
from .azure_openai.azure_openai_request_builder import Azure_openaiRequestBuilder
18+
from .azure_storage.azure_storage_request_builder import Azure_storageRequestBuilder
1819
from .document_intelligence.document_intelligence_request_builder import Document_intelligenceRequestBuilder
1920

2021
class ApiClient(BaseRequestBuilder):
@@ -50,6 +51,15 @@ def azure_openai(self) -> Azure_openaiRequestBuilder:
5051

5152
return Azure_openaiRequestBuilder(self.request_adapter, self.path_parameters)
5253

54+
@property
55+
def azure_storage(self) -> Azure_storageRequestBuilder:
56+
"""
57+
The azure_storage property
58+
"""
59+
from .azure_storage.azure_storage_request_builder import Azure_storageRequestBuilder
60+
61+
return Azure_storageRequestBuilder(self.request_adapter, self.path_parameters)
62+
5363
@property
5464
def document_intelligence(self) -> Document_intelligenceRequestBuilder:
5565
"""
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from __future__ import annotations
2+
from kiota_abstractions.base_request_builder import BaseRequestBuilder
3+
from kiota_abstractions.get_path_parameters import get_path_parameters
4+
from kiota_abstractions.request_adapter import RequestAdapter
5+
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union
6+
7+
if TYPE_CHECKING:
8+
from .blobs.blobs_request_builder import BlobsRequestBuilder
9+
10+
class Azure_storageRequestBuilder(BaseRequestBuilder):
11+
"""
12+
Builds and executes requests for operations under /azure_storage
13+
"""
14+
def __init__(self,request_adapter: RequestAdapter, path_parameters: Union[str, Dict[str, Any]]) -> None:
15+
"""
16+
Instantiates a new Azure_storageRequestBuilder and sets the default values.
17+
param path_parameters: The raw url or the url-template parameters for the request.
18+
param request_adapter: The request adapter to use to execute the requests.
19+
Returns: None
20+
"""
21+
super().__init__(request_adapter, "{+baseurl}/azure_storage", path_parameters)
22+
23+
@property
24+
def blobs(self) -> BlobsRequestBuilder:
25+
"""
26+
The blobs property
27+
"""
28+
from .blobs.blobs_request_builder import BlobsRequestBuilder
29+
30+
return BlobsRequestBuilder(self.request_adapter, self.path_parameters)
31+
32+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from __future__ import annotations
2+
from kiota_abstractions.base_request_builder import BaseRequestBuilder
3+
from kiota_abstractions.base_request_configuration import RequestConfiguration
4+
from kiota_abstractions.get_path_parameters import get_path_parameters
5+
from kiota_abstractions.method import Method
6+
from kiota_abstractions.request_adapter import RequestAdapter
7+
from kiota_abstractions.request_information import RequestInformation
8+
from kiota_abstractions.request_option import RequestOption
9+
from kiota_abstractions.serialization import Parsable, ParsableFactory
10+
from typing import Any, Callable, Dict, List, Optional, TYPE_CHECKING, Union
11+
12+
if TYPE_CHECKING:
13+
from .delete.delete_request_builder import DeleteRequestBuilder
14+
from .upload.upload_request_builder import UploadRequestBuilder
15+
16+
class BlobsRequestBuilder(BaseRequestBuilder):
17+
"""
18+
Builds and executes requests for operations under /azure_storage/blobs
19+
"""
20+
def __init__(self,request_adapter: RequestAdapter, path_parameters: Union[str, Dict[str, Any]]) -> None:
21+
"""
22+
Instantiates a new BlobsRequestBuilder and sets the default values.
23+
param path_parameters: The raw url or the url-template parameters for the request.
24+
param request_adapter: The request adapter to use to execute the requests.
25+
Returns: None
26+
"""
27+
super().__init__(request_adapter, "{+baseurl}/azure_storage/blobs", path_parameters)
28+
29+
async def get(self,request_configuration: Optional[RequestConfiguration] = None) -> Optional[UntypedNode]:
30+
"""
31+
List Blobs
32+
param request_configuration: Configuration for the request such as headers, query parameters, and middleware options.
33+
Returns: Optional[UntypedNode]
34+
"""
35+
request_info = self.to_get_request_information(
36+
request_configuration
37+
)
38+
if not self.request_adapter:
39+
raise Exception("Http core is null")
40+
return await self.request_adapter.send_async(request_info, UntypedNode, None)
41+
42+
def to_get_request_information(self,request_configuration: Optional[RequestConfiguration] = None) -> RequestInformation:
43+
"""
44+
List Blobs
45+
param request_configuration: Configuration for the request such as headers, query parameters, and middleware options.
46+
Returns: RequestInformation
47+
"""
48+
request_info = RequestInformation(Method.GET, self.url_template, self.path_parameters)
49+
request_info.configure(request_configuration)
50+
request_info.headers.try_add("Accept", "application/json")
51+
return request_info
52+
53+
def with_url(self,raw_url: Optional[str] = None) -> BlobsRequestBuilder:
54+
"""
55+
Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
56+
param raw_url: The raw URL to use for the request builder.
57+
Returns: BlobsRequestBuilder
58+
"""
59+
if not raw_url:
60+
raise TypeError("raw_url cannot be null.")
61+
return BlobsRequestBuilder(self.request_adapter, raw_url)
62+
63+
@property
64+
def delete_path(self) -> DeleteRequestBuilder:
65+
"""
66+
The deletePath property
67+
"""
68+
from .delete.delete_request_builder import DeleteRequestBuilder
69+
70+
return DeleteRequestBuilder(self.request_adapter, self.path_parameters)
71+
72+
@property
73+
def upload(self) -> UploadRequestBuilder:
74+
"""
75+
The upload property
76+
"""
77+
from .upload.upload_request_builder import UploadRequestBuilder
78+
79+
return UploadRequestBuilder(self.request_adapter, self.path_parameters)
80+
81+

0 commit comments

Comments
 (0)