dcoink 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dcoink/__init__.py +52 -0
- dcoink/async_client.py +113 -0
- dcoink/cli.py +34 -0
- dcoink/client.py +114 -0
- dcoink/errors.py +29 -0
- dcoink/models.py +35 -0
- dcoink-0.1.0.dist-info/METADATA +104 -0
- dcoink-0.1.0.dist-info/RECORD +10 -0
- dcoink-0.1.0.dist-info/WHEEL +4 -0
- dcoink-0.1.0.dist-info/entry_points.txt +2 -0
dcoink/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dcoink - The official Python SDK for dco.ink
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .models import Link, UserInfo
|
|
9
|
+
from .client import Client, BASE_URL
|
|
10
|
+
from .async_client import AsyncClient
|
|
11
|
+
from . import errors
|
|
12
|
+
|
|
13
|
+
def shorten(url: str, custom_code: Optional[str] = None, api_key: Optional[str] = None, base_url: str = BASE_URL) -> Link:
|
|
14
|
+
"""
|
|
15
|
+
Intelligently shortens a URL.
|
|
16
|
+
- If `api_key` is provided, calls POST /api/links (authenticated, allows custom codes).
|
|
17
|
+
- If `api_key` is NOT provided, calls GET /api/s (fast anonymous mode).
|
|
18
|
+
"""
|
|
19
|
+
if custom_code and not api_key:
|
|
20
|
+
raise errors.ValidationError("custom_code requires an api_key to be provided.")
|
|
21
|
+
|
|
22
|
+
if api_key:
|
|
23
|
+
# Authenticated Mode
|
|
24
|
+
with Client(api_key=api_key, base_url=base_url) as client:
|
|
25
|
+
return client.create_link(url, custom_code=custom_code)
|
|
26
|
+
else:
|
|
27
|
+
# Fast Anonymous Mode
|
|
28
|
+
response = httpx.get(f"{base_url}/api/s", params={"url": url})
|
|
29
|
+
|
|
30
|
+
if not response.is_success:
|
|
31
|
+
# Handle specific known errors from the plain-text endpoint
|
|
32
|
+
if response.text.startswith("error: invalid_url"):
|
|
33
|
+
raise errors.ValidationError("Please provide a valid URL.", status_code=response.status_code)
|
|
34
|
+
raise errors.DcoApiError(f"Failed to create anonymous link: {response.text}", status_code=response.status_code)
|
|
35
|
+
|
|
36
|
+
short_url = response.text.strip()
|
|
37
|
+
short_code = short_url.split("/")[-1]
|
|
38
|
+
|
|
39
|
+
return Link(
|
|
40
|
+
short_code=short_code,
|
|
41
|
+
short_url=short_url,
|
|
42
|
+
target_url=url
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"Client",
|
|
47
|
+
"AsyncClient",
|
|
48
|
+
"Link",
|
|
49
|
+
"UserInfo",
|
|
50
|
+
"shorten",
|
|
51
|
+
"errors",
|
|
52
|
+
]
|
dcoink/async_client.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import List, Optional, Dict, Any
|
|
3
|
+
from .models import Link, UserInfo
|
|
4
|
+
from .errors import (
|
|
5
|
+
DcoApiError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
ValidationError,
|
|
8
|
+
CodeTakenError,
|
|
9
|
+
ForbiddenError,
|
|
10
|
+
RateLimitError
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
BASE_URL = "https://api.dco.ink"
|
|
14
|
+
|
|
15
|
+
class AsyncClient:
|
|
16
|
+
"""Asynchronous client for the dco.ink API."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str, base_url: str = BASE_URL, timeout: float = 10.0):
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.base_url = base_url
|
|
21
|
+
self._client = httpx.AsyncClient(
|
|
22
|
+
base_url=self.base_url,
|
|
23
|
+
headers={
|
|
24
|
+
"X-API-Key": self.api_key,
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
"User-Agent": "dcoink-python-sdk/0.1.0"
|
|
27
|
+
},
|
|
28
|
+
timeout=timeout
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _handle_error(self, response: httpx.Response):
|
|
32
|
+
try:
|
|
33
|
+
data = response.json()
|
|
34
|
+
error_code = data.get("error", "unknown_error")
|
|
35
|
+
message = data.get("message", response.text)
|
|
36
|
+
except Exception:
|
|
37
|
+
error_code = "unknown_error"
|
|
38
|
+
message = response.text
|
|
39
|
+
|
|
40
|
+
status = response.status_code
|
|
41
|
+
if status == 401:
|
|
42
|
+
raise AuthenticationError(message, error_code, status)
|
|
43
|
+
elif status == 403:
|
|
44
|
+
raise ForbiddenError(message, error_code, status)
|
|
45
|
+
elif status == 409:
|
|
46
|
+
raise CodeTakenError(message, error_code, status)
|
|
47
|
+
elif status == 429:
|
|
48
|
+
raise RateLimitError("Rate limit exceeded", error_code, status)
|
|
49
|
+
elif status == 400:
|
|
50
|
+
raise ValidationError(message, error_code, status)
|
|
51
|
+
else:
|
|
52
|
+
raise DcoApiError(message, error_code, status)
|
|
53
|
+
|
|
54
|
+
async def _request(self, method: str, path: str, **kwargs) -> Any:
|
|
55
|
+
response = await self._client.request(method, path, **kwargs)
|
|
56
|
+
if not response.is_success:
|
|
57
|
+
self._handle_error(response)
|
|
58
|
+
|
|
59
|
+
if response.status_code == 204 or not response.content:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
return response.json()
|
|
63
|
+
|
|
64
|
+
async def get_me(self) -> UserInfo:
|
|
65
|
+
"""Get the current authenticated user's details."""
|
|
66
|
+
data = await self._request("GET", "/api/auth/me")
|
|
67
|
+
return UserInfo.from_dict(data)
|
|
68
|
+
|
|
69
|
+
async def create_link(self, url: str, custom_code: Optional[str] = None) -> Link:
|
|
70
|
+
"""Create a new shortened link."""
|
|
71
|
+
payload = {"url": url}
|
|
72
|
+
if custom_code:
|
|
73
|
+
payload["custom_code"] = custom_code
|
|
74
|
+
|
|
75
|
+
data = await self._request("POST", "/api/links", json=payload)
|
|
76
|
+
return Link(
|
|
77
|
+
short_code=data["short_code"],
|
|
78
|
+
short_url=data["short_url"],
|
|
79
|
+
target_url=data.get("target_url", url),
|
|
80
|
+
expires_at=data.get("expires_at")
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def list_links(self, limit: int = 50, offset: int = 0) -> List[Link]:
|
|
84
|
+
"""List all links created by the current user."""
|
|
85
|
+
data = await self._request("GET", "/api/links", params={"limit": limit, "offset": offset})
|
|
86
|
+
links = []
|
|
87
|
+
for item in data.get("links", []):
|
|
88
|
+
links.append(Link(
|
|
89
|
+
short_code=item["short_code"],
|
|
90
|
+
short_url=f"https://dco.ink/{item['short_code']}",
|
|
91
|
+
target_url=item.get("target_url"),
|
|
92
|
+
created_at=item.get("created_at"),
|
|
93
|
+
expires_at=item.get("expires_at")
|
|
94
|
+
))
|
|
95
|
+
return links
|
|
96
|
+
|
|
97
|
+
async def update_link(self, short_code: str, new_url: str) -> None:
|
|
98
|
+
"""Update the target URL of an existing link."""
|
|
99
|
+
await self._request("PUT", f"/api/links/{short_code}", json={"url": new_url})
|
|
100
|
+
|
|
101
|
+
async def delete_link(self, short_code: str) -> None:
|
|
102
|
+
"""Delete an existing link."""
|
|
103
|
+
await self._request("DELETE", f"/api/links/{short_code}")
|
|
104
|
+
|
|
105
|
+
async def close(self):
|
|
106
|
+
"""Close the underlying HTTP client."""
|
|
107
|
+
await self._client.aclose()
|
|
108
|
+
|
|
109
|
+
async def __aenter__(self):
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
113
|
+
await self.close()
|
dcoink/cli.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import dcoink
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = argparse.ArgumentParser(description="dco.ink - Minimalist URL Shortener CLI")
|
|
7
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
8
|
+
|
|
9
|
+
# shorten subcommand
|
|
10
|
+
shorten_parser = subparsers.add_parser("shorten", help="Shorten a URL")
|
|
11
|
+
shorten_parser.add_argument("url", help="The long URL to shorten")
|
|
12
|
+
shorten_parser.add_argument("-c", "--code", help="Custom short code (requires API Key)", default=None)
|
|
13
|
+
shorten_parser.add_argument("-k", "--key", help="Your dco.ink API Key (optional for random links)", default=None)
|
|
14
|
+
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
if args.command is None:
|
|
18
|
+
parser.print_help()
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
link = dcoink.shorten(url=args.url, custom_code=args.code, api_key=args.key)
|
|
23
|
+
print(f"✅ Success! Your short link is ready:")
|
|
24
|
+
print(f"👉 {link.short_url}")
|
|
25
|
+
except dcoink.errors.DcoApiError as e:
|
|
26
|
+
print(f"❌ API Error: {e.message}")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"❌ Unexpected Error: {str(e)}")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|
|
34
|
+
|
dcoink/client.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import List, Optional, Dict, Any
|
|
3
|
+
from .models import Link, UserInfo
|
|
4
|
+
from .errors import (
|
|
5
|
+
DcoApiError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
ValidationError,
|
|
8
|
+
CodeTakenError,
|
|
9
|
+
ForbiddenError,
|
|
10
|
+
RateLimitError
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
BASE_URL = "https://api.dco.ink"
|
|
14
|
+
|
|
15
|
+
class Client:
|
|
16
|
+
"""Synchronous client for the dco.ink API."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str, base_url: str = BASE_URL, timeout: float = 10.0):
|
|
19
|
+
self.api_key = api_key
|
|
20
|
+
self.base_url = base_url
|
|
21
|
+
self._client = httpx.Client(
|
|
22
|
+
base_url=self.base_url,
|
|
23
|
+
headers={
|
|
24
|
+
"X-API-Key": self.api_key,
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
"User-Agent": "dcoink-python-sdk/0.1.0"
|
|
27
|
+
},
|
|
28
|
+
timeout=timeout
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def _handle_error(self, response: httpx.Response):
|
|
32
|
+
try:
|
|
33
|
+
data = response.json()
|
|
34
|
+
error_code = data.get("error", "unknown_error")
|
|
35
|
+
message = data.get("message", response.text)
|
|
36
|
+
except Exception:
|
|
37
|
+
error_code = "unknown_error"
|
|
38
|
+
message = response.text
|
|
39
|
+
|
|
40
|
+
status = response.status_code
|
|
41
|
+
if status == 401:
|
|
42
|
+
raise AuthenticationError(message, error_code, status)
|
|
43
|
+
elif status == 403:
|
|
44
|
+
raise ForbiddenError(message, error_code, status)
|
|
45
|
+
elif status == 409:
|
|
46
|
+
raise CodeTakenError(message, error_code, status)
|
|
47
|
+
elif status == 429:
|
|
48
|
+
raise RateLimitError("Rate limit exceeded", error_code, status)
|
|
49
|
+
elif status == 400:
|
|
50
|
+
raise ValidationError(message, error_code, status)
|
|
51
|
+
else:
|
|
52
|
+
raise DcoApiError(message, error_code, status)
|
|
53
|
+
|
|
54
|
+
def _request(self, method: str, path: str, **kwargs) -> Any:
|
|
55
|
+
response = self._client.request(method, path, **kwargs)
|
|
56
|
+
if not response.is_success:
|
|
57
|
+
self._handle_error(response)
|
|
58
|
+
|
|
59
|
+
# 204 No Content handling
|
|
60
|
+
if response.status_code == 204 or not response.content:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
return response.json()
|
|
64
|
+
|
|
65
|
+
def get_me(self) -> UserInfo:
|
|
66
|
+
"""Get the current authenticated user's details."""
|
|
67
|
+
data = self._request("GET", "/api/auth/me")
|
|
68
|
+
return UserInfo.from_dict(data)
|
|
69
|
+
|
|
70
|
+
def create_link(self, url: str, custom_code: Optional[str] = None) -> Link:
|
|
71
|
+
"""Create a new shortened link."""
|
|
72
|
+
payload = {"url": url}
|
|
73
|
+
if custom_code:
|
|
74
|
+
payload["custom_code"] = custom_code
|
|
75
|
+
|
|
76
|
+
data = self._request("POST", "/api/links", json=payload)
|
|
77
|
+
return Link(
|
|
78
|
+
short_code=data["short_code"],
|
|
79
|
+
short_url=data["short_url"],
|
|
80
|
+
target_url=data.get("target_url", url),
|
|
81
|
+
expires_at=data.get("expires_at")
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def list_links(self, limit: int = 50, offset: int = 0) -> List[Link]:
|
|
85
|
+
"""List all links created by the current user."""
|
|
86
|
+
data = self._request("GET", "/api/links", params={"limit": limit, "offset": offset})
|
|
87
|
+
links = []
|
|
88
|
+
for item in data.get("links", []):
|
|
89
|
+
links.append(Link(
|
|
90
|
+
short_code=item["short_code"],
|
|
91
|
+
short_url=f"https://dco.ink/{item['short_code']}",
|
|
92
|
+
target_url=item.get("target_url"),
|
|
93
|
+
created_at=item.get("created_at"),
|
|
94
|
+
expires_at=item.get("expires_at")
|
|
95
|
+
))
|
|
96
|
+
return links
|
|
97
|
+
|
|
98
|
+
def update_link(self, short_code: str, new_url: str) -> None:
|
|
99
|
+
"""Update the target URL of an existing link."""
|
|
100
|
+
self._request("PUT", f"/api/links/{short_code}", json={"url": new_url})
|
|
101
|
+
|
|
102
|
+
def delete_link(self, short_code: str) -> None:
|
|
103
|
+
"""Delete an existing link."""
|
|
104
|
+
self._request("DELETE", f"/api/links/{short_code}")
|
|
105
|
+
|
|
106
|
+
def close(self):
|
|
107
|
+
"""Close the underlying HTTP client."""
|
|
108
|
+
self._client.close()
|
|
109
|
+
|
|
110
|
+
def __enter__(self):
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
114
|
+
self.close()
|
dcoink/errors.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
class DcoApiError(Exception):
|
|
4
|
+
"""Base class for all API errors."""
|
|
5
|
+
def __init__(self, message: str, code: Optional[str] = None, status_code: Optional[int] = None):
|
|
6
|
+
super().__init__(message)
|
|
7
|
+
self.message = message
|
|
8
|
+
self.code = code
|
|
9
|
+
self.status_code = status_code
|
|
10
|
+
|
|
11
|
+
class AuthenticationError(DcoApiError):
|
|
12
|
+
"""Raised when the API key is missing or invalid."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class ValidationError(DcoApiError):
|
|
16
|
+
"""Raised when the provided URL or custom code is invalid."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
class CodeTakenError(DcoApiError):
|
|
20
|
+
"""Raised when the requested custom code is already in use."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
class ForbiddenError(DcoApiError):
|
|
24
|
+
"""Raised when the user attempts an action they do not have permission for."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class RateLimitError(DcoApiError):
|
|
28
|
+
"""Raised when the API rate limit is exceeded."""
|
|
29
|
+
pass
|
dcoink/models.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Link:
|
|
6
|
+
"""Represents a shortened link."""
|
|
7
|
+
short_code: str
|
|
8
|
+
short_url: str
|
|
9
|
+
target_url: Optional[str] = None
|
|
10
|
+
id: Optional[str] = None
|
|
11
|
+
clicks: int = 0
|
|
12
|
+
created_at: Optional[str] = None
|
|
13
|
+
expires_at: Optional[str] = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_dict(cls, data: dict) -> 'Link':
|
|
17
|
+
valid_keys = {k for k in cls.__annotations__}
|
|
18
|
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
|
19
|
+
return cls(**filtered)
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class UserInfo:
|
|
23
|
+
"""Represents the authenticated user's details."""
|
|
24
|
+
id: str
|
|
25
|
+
email: str
|
|
26
|
+
name: str
|
|
27
|
+
is_subscribed: int
|
|
28
|
+
api_token: str
|
|
29
|
+
avatar_url: Optional[str] = None
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_dict(cls, data: dict) -> 'UserInfo':
|
|
33
|
+
valid_keys = {k for k in cls.__annotations__}
|
|
34
|
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
|
35
|
+
return cls(**filtered)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dcoink
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The official Python SDK for dco.ink - A minimalist and developer-friendly URL shortener
|
|
5
|
+
Project-URL: Homepage, https://dco.ink
|
|
6
|
+
Project-URL: Source, https://github.com/dco/dco-ink-tools
|
|
7
|
+
Author-email: "dco.ink" <hello@dco.ink>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Requires-Dist: httpx>=0.24.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# dcoink - Official Python SDK for dco.ink
|
|
17
|
+
|
|
18
|
+
The official, beautifully typed Python SDK for [dco.ink](https://dco.ink) - the minimalist, privacy-first URL shortener.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install dcoink
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start (Anonymous Mode)
|
|
27
|
+
You don't even need an account to start shortening URLs! The SDK provides a blazing-fast anonymous mode.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import dcoink
|
|
31
|
+
|
|
32
|
+
link = dcoink.shorten("https://example.com/a-very-long-url-that-needs-shortening")
|
|
33
|
+
|
|
34
|
+
print(f"Short URL: {link.short_url}")
|
|
35
|
+
# Output: https://dco.ink/xyz123
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Authenticated Mode
|
|
39
|
+
By passing your API Key, you can manage your links and access advanced features (like specifying custom short codes).
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import dcoink
|
|
43
|
+
|
|
44
|
+
link = dcoink.shorten(
|
|
45
|
+
"https://example.com/my-campaign",
|
|
46
|
+
custom_code="mybrand",
|
|
47
|
+
api_key="dco_xxxxxx"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
print(link.short_url)
|
|
51
|
+
# Output: https://dco.ink/mybrand
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Advanced Usage (Client API)
|
|
55
|
+
|
|
56
|
+
For robust applications, use the `Client` to manage your links, check history, and more.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from dcoink import Client
|
|
60
|
+
|
|
61
|
+
with Client(api_key="dco_xxxxxx") as client:
|
|
62
|
+
# Get user info
|
|
63
|
+
me = client.get_me()
|
|
64
|
+
print(f"Logged in as {me.name}")
|
|
65
|
+
|
|
66
|
+
# Create a link
|
|
67
|
+
link = client.create_link("https://github.com", custom_code="git")
|
|
68
|
+
|
|
69
|
+
# List history
|
|
70
|
+
links = client.list_links(limit=10)
|
|
71
|
+
for l in links:
|
|
72
|
+
print(l.short_url, l.clicks)
|
|
73
|
+
|
|
74
|
+
# Update and delete
|
|
75
|
+
client.update_link("git", "https://github.com/new-target")
|
|
76
|
+
client.delete_link("git")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Async Support
|
|
80
|
+
|
|
81
|
+
Building high-concurrency scrapers or bots? We've got you covered with `AsyncClient` powered by `httpx`.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import asyncio
|
|
85
|
+
from dcoink import AsyncClient
|
|
86
|
+
|
|
87
|
+
async def main():
|
|
88
|
+
async with AsyncClient(api_key="dco_xxxxxx") as client:
|
|
89
|
+
link = await client.create_link("https://example.com")
|
|
90
|
+
print(link.short_url)
|
|
91
|
+
|
|
92
|
+
asyncio.run(main())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Built-in CLI Tool
|
|
96
|
+
When you install the SDK, you also get a neat terminal command!
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Quick shorten
|
|
100
|
+
dcoink shorten https://example.com
|
|
101
|
+
|
|
102
|
+
# Custom short code
|
|
103
|
+
dcoink shorten https://example.com -c mybrand -k dco_xxxxxx
|
|
104
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dcoink/__init__.py,sha256=uNpaoXq3scdRMPsuL1LShYOeuDAW0joML8dNj05beqg,1748
|
|
2
|
+
dcoink/async_client.py,sha256=apjDYZlSBIE5Qk55vVXpHdpRrS3GKLnQItKgFvkXatE,4062
|
|
3
|
+
dcoink/cli.py,sha256=hUtAJYCTRSMDTXrrwQc9ZdIu6upIAbIT8SaSX78LnSg,1152
|
|
4
|
+
dcoink/client.py,sha256=NGKUZ-yXsNEelsTSvuqRftExy-RRhZXcDQ9ApY4yBNs,3980
|
|
5
|
+
dcoink/errors.py,sha256=T2uI7fMuMgeocAbc-Btyoz6-w9nd42NoCSomvSb9AwI,888
|
|
6
|
+
dcoink/models.py,sha256=VjizoIKGNy8spJf4JEYjRbJMnrRjC1uI0Prf0OdPF_s,983
|
|
7
|
+
dcoink-0.1.0.dist-info/METADATA,sha256=sCdcRTXPU1OaLruRLjYaGM4mMse7k6kW4egGzzetFik,2671
|
|
8
|
+
dcoink-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
9
|
+
dcoink-0.1.0.dist-info/entry_points.txt,sha256=xlcuNeu5YhbQeBMsEjMrSD5qIDjRBB4ngbQgAJyBQyg,43
|
|
10
|
+
dcoink-0.1.0.dist-info/RECORD,,
|