Skip to content

Commit 5b6ced6

Browse files
Merge pull request #70 from Utkarsh4517/feat-sdk
Feat: Created SDK
2 parents 2e7cbf2 + cd153d9 commit 5b6ced6

File tree

9 files changed

+234
-5
lines changed

9 files changed

+234
-5
lines changed

.pdm-python

Lines changed: 0 additions & 1 deletion
This file was deleted.

sdk/example.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from sdk.module import PasteBinSDK
2+
3+
def test_pastebin_sdk():
4+
sdk = PasteBinSDK()
5+
6+
try:
7+
# Create a paste
8+
paste_id = sdk.create_paste("print('Hello, World!')", ".py")
9+
print(f"Created paste with ID: {paste_id}")
10+
11+
# Retrieve the paste
12+
content = sdk.get_paste(paste_id)
13+
print(f"Retrieved paste content: {content}")
14+
15+
# Delete the paste
16+
result = sdk.delete_paste(paste_id)
17+
print(f"Delete result: {result}")
18+
19+
# Get supported languages
20+
languages = sdk.get_languages()
21+
print(f"Number of supported languages: {len(languages)}")
22+
23+
except RuntimeError as e:
24+
print(f"An error occurred: {e}")
25+
26+
if __name__ == "__main__":
27+
test_pastebin_sdk()

sdk/readme.md

Whitespace-only changes.

sdk/sdk/__init__.py

Whitespace-only changes.

sdk/sdk/module.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import requests
2+
from typing import Optional, Union
3+
from pathlib import Path
4+
5+
class PasteBinSDK:
6+
def __init__(self, base_url: str = "https://paste.fosscu.org"):
7+
self.base_url = base_url
8+
9+
def create_paste(self, content: Union[str, Path], file_extension: str) -> str:
10+
"""
11+
Create a new paste.
12+
:param content: The content to paste, either as a string or a Path to a file
13+
:param file_extension: File extension for syntax highlighting (required)
14+
:return: The unique identifier of the created paste
15+
"""
16+
try:
17+
if isinstance(content, Path):
18+
with open(content, 'r', encoding='utf-8') as f:
19+
content = f.read()
20+
21+
data = {
22+
'content': content,
23+
'extension': file_extension
24+
}
25+
response = requests.post(f"{self.base_url}/api/paste", json=data)
26+
response.raise_for_status()
27+
result = response.json()
28+
return result['uuid']
29+
except requests.RequestException as e:
30+
raise RuntimeError(f"Error creating paste: {str(e)}")
31+
32+
def get_paste(self, uuid: str) -> dict:
33+
"""
34+
Retrieve a paste by its unique identifier.
35+
:param uuid: The unique identifier of the paste
36+
:return: A dictionary containing the paste details (uuid, content, extension)
37+
"""
38+
try:
39+
response = requests.get(f"{self.base_url}/api/paste/{uuid}")
40+
response.raise_for_status()
41+
return response.json()
42+
except requests.RequestException as e:
43+
raise RuntimeError(f"Error retrieving paste: {str(e)}")
44+
45+
def delete_paste(self, uuid: str) -> str:
46+
"""
47+
Delete a paste by its unique identifier.
48+
:param uuid: The unique identifier of the paste
49+
:return: A confirmation message
50+
"""
51+
try:
52+
response = requests.delete(f"{self.base_url}/paste/{uuid}")
53+
response.raise_for_status()
54+
return response.text
55+
except requests.RequestException as e:
56+
raise RuntimeError(f"Error deleting paste: {str(e)}")
57+
58+
def get_languages(self) -> dict:
59+
"""
60+
Get the list of supported languages for syntax highlighting.
61+
:return: A dictionary of supported languages
62+
"""
63+
try:
64+
response = requests.get(f"{self.base_url}/languages.json")
65+
response.raise_for_status()
66+
return response.json()
67+
except requests.RequestException as e:
68+
raise RuntimeError(f"Error fetching languages: {str(e)}")

sdk/setup.py

Whitespace-only changes.

src/paste/main.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
RedirectResponse,
1616
JSONResponse,
1717
)
18+
from starlette.requests import Request
19+
from starlette.responses import Response
20+
from typing import Callable, Awaitable, List, Optional, Union, Any
1821
import shutil
1922
import os
2023
import json
@@ -30,8 +33,8 @@
3033
from pygments.lexers import get_lexer_by_name, guess_lexer
3134
from pygments.formatters import HtmlFormatter
3235
from pygments.util import ClassNotFound
33-
from typing import List, Optional
3436
from . import __version__, __author__, __contact__, __url__
37+
from .schema import PasteCreate, PasteResponse, PasteDetails
3538

3639
description: str = "paste.py 🐍 - A pastebin written in python."
3740

@@ -50,7 +53,20 @@
5053
redoc_url=None,
5154
)
5255
app.state.limiter = limiter
53-
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
56+
57+
58+
def rate_limit_exceeded_handler(request: Request, exc: Exception) -> Union[Response, Awaitable[Response]]:
59+
if isinstance(exc, RateLimitExceeded):
60+
return Response(
61+
content="Rate limit exceeded",
62+
status_code=429
63+
)
64+
return Response(
65+
content="An error occurred",
66+
status_code=500
67+
)
68+
69+
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
5470

5571
origins: List[str] = ["*"]
5672

@@ -306,3 +322,64 @@ async def get_languages() -> JSONResponse:
306322
detail=f"Error reading languages file: {e}",
307323
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
308324
)
325+
326+
# apis to create and get a paste which returns uuid and url (to be used by SDK)
327+
@app.post("/api/paste", response_model=PasteResponse)
328+
async def create_paste(paste: PasteCreate) -> JSONResponse:
329+
try:
330+
uuid: str = generate_uuid()
331+
if uuid in large_uuid_storage:
332+
uuid = generate_uuid()
333+
334+
uuid_with_extension: str = f"{uuid}.{paste.extension}"
335+
path: str = f"data/{uuid_with_extension}"
336+
337+
with open(path, "w", encoding="utf-8") as f:
338+
f.write(paste.content)
339+
340+
large_uuid_storage.append(uuid_with_extension)
341+
342+
return JSONResponse(
343+
content=PasteResponse(
344+
uuid=uuid_with_extension,
345+
url=f"{BASE_URL}/paste/{uuid_with_extension}"
346+
).dict(),
347+
status_code=status.HTTP_201_CREATED
348+
)
349+
except Exception as e:
350+
raise HTTPException(
351+
detail=f"There was an error creating the paste: {str(e)}",
352+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
353+
)
354+
355+
@app.get("/api/paste/{uuid}", response_model=PasteDetails)
356+
async def get_paste_details(uuid: str) -> JSONResponse:
357+
if not "." in uuid:
358+
uuid = _find_without_extension(uuid)
359+
path: str = f"data/{uuid}"
360+
361+
try:
362+
with open(path, "r", encoding="utf-8") as f:
363+
content: str = f.read()
364+
365+
extension: str = Path(path).suffix[1:]
366+
367+
return JSONResponse(
368+
content=PasteDetails(
369+
uuid=uuid,
370+
content=content,
371+
extension=extension
372+
).dict(),
373+
status_code=status.HTTP_200_OK
374+
)
375+
except FileNotFoundError:
376+
raise HTTPException(
377+
detail="Paste not found",
378+
status_code=status.HTTP_404_NOT_FOUND,
379+
)
380+
except Exception as e:
381+
raise HTTPException(
382+
detail=f"Error retrieving paste: {str(e)}",
383+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
384+
)
385+

src/paste/schema.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1+
from typing import Optional
12
from pydantic import BaseModel
23

3-
44
class Data(BaseModel):
5-
input_data: str
5+
input_data: str
6+
7+
class PasteCreate(BaseModel):
8+
content: str
9+
extension: Optional[str] = None
10+
11+
class PasteResponse(BaseModel):
12+
uuid: str
13+
url: str
14+
15+
class PasteDetails(BaseModel):
16+
uuid: str
17+
content: str
18+
extension: Optional[str] = None

tests/test_api.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,48 @@ def test_post_file_route_size_limit() -> None:
9090
os.remove(large_file_name)
9191
assert response.status_code == 413
9292
assert "File is too large" in response.text
93+
94+
def test_post_api_paste_route() -> None:
95+
paste_data = {
96+
"content": "This is a test paste content",
97+
"extension": "txt"
98+
}
99+
response = client.post("/api/paste", json=paste_data)
100+
assert response.status_code == 201
101+
response_json = response.json()
102+
assert "uuid" in response_json
103+
assert "url" in response_json
104+
assert response_json["uuid"].endswith(".txt")
105+
assert response_json["url"].startswith("http://paste.fosscu.org/paste/")
106+
107+
# Clean up: delete the created paste
108+
uuid = response_json["uuid"]
109+
delete_response = client.delete(f"/paste/{uuid}")
110+
assert delete_response.status_code == 200
111+
112+
def test_get_api_paste_route() -> None:
113+
# First, create a paste
114+
paste_data = {
115+
"content": "This is a test paste content for GET",
116+
"extension": "md"
117+
}
118+
create_response = client.post("/api/paste", json=paste_data)
119+
assert create_response.status_code == 201
120+
created_uuid = create_response.json()["uuid"]
121+
122+
# Now, test getting the paste
123+
response = client.get(f"/api/paste/{created_uuid}")
124+
assert response.status_code == 200
125+
response_json = response.json()
126+
assert response_json["uuid"] == created_uuid
127+
assert response_json["content"] == paste_data["content"]
128+
assert response_json["extension"] == paste_data["extension"]
129+
130+
# Clean up: delete the created paste
131+
delete_response = client.delete(f"/paste/{created_uuid}")
132+
assert delete_response.status_code == 200
133+
134+
def test_get_api_paste_route_not_found() -> None:
135+
response = client.get("/api/paste/nonexistent_uuid.txt")
136+
assert response.status_code == 404
137+
assert response.json()["detail"] == "Paste not found"

0 commit comments

Comments
 (0)