From 0a6a0f06636876d4026878e59f8c0dc48b80a09d Mon Sep 17 00:00:00 2001
From: Den Rozhnovskiy
Date: Fri, 10 Jan 2025 01:52:51 +0500
Subject: [PATCH] docs: added pdoc docs
---
.github/workflows/docs.yml | 49 +
docs/index.html | 7 +
docs/pyoutlineapi.html | 2833 ++++++++++++++++++++++++++++++++++++
docs/search.js | 46 +
4 files changed, 2935 insertions(+)
create mode 100644 .github/workflows/docs.yml
create mode 100644 docs/index.html
create mode 100644 docs/pyoutlineapi.html
create mode 100644 docs/search.js
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..b59f858
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,49 @@
+name: website
+
+# build the documentation whenever there are new commits on main
+on:
+ push:
+ branches:
+ - development
+ # Alternative: only build for tags.
+ # tags:
+ # - '*'
+
+# security: restrict permissions for CI jobs.
+permissions:
+ contents: read
+
+jobs:
+ # Build the documentation and upload the static HTML files as an artifact.
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.13'
+
+ # ADJUST THIS: install all dependencies (including pdoc)
+ - run: pip install -e .
+ # ADJUST THIS: build your documentation into docs/.
+ # We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here.
+ - run: python docs/make.py
+
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs/
+
+ # Deploy the artifact to GitHub pages.
+ # This is a separate job so that only actions/deploy-pages has the necessary permissions.
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ permissions:
+ pages: write
+ id-token: write
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - id: deployment
+ uses: actions/deploy-pages@v4
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..f272e85
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/docs/pyoutlineapi.html b/docs/pyoutlineapi.html
new file mode 100644
index 0000000..68f939b
--- /dev/null
+++ b/docs/pyoutlineapi.html
@@ -0,0 +1,2833 @@
+
+
+
+
+
+
+ pyoutlineapi API documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+pyoutlineapi
+
+
+
+
+ View Source
+
+ 1 from .client import AsyncOutlineClient , OutlineError , APIError
+ 2 from .models import (
+ 3 AccessKey ,
+ 4 AccessKeyCreateRequest ,
+ 5 AccessKeyList ,
+ 6 DataLimit ,
+ 7 ErrorResponse ,
+ 8 ExperimentalMetrics ,
+ 9 MetricsPeriod ,
+10 MetricsStatusResponse ,
+11 Server ,
+12 ServerMetrics ,
+13 )
+14
+15 __version__ = "0.2.0"
+16
+17 __all__ = [
+18 "AsyncOutlineClient" ,
+19 "OutlineError" ,
+20 "APIError" ,
+21 "AccessKey" ,
+22 "AccessKeyCreateRequest" ,
+23 "AccessKeyList" ,
+24 "DataLimit" ,
+25 "ErrorResponse" ,
+26 "ExperimentalMetrics" ,
+27 "MetricsPeriod" ,
+28 "MetricsStatusResponse" ,
+29 "Server" ,
+30 "ServerMetrics" ,
+31 ]
+
+
+
+
+
+
+
+
+ class
+ AsyncOutlineClient :
+
+ View Source
+
+
+
+ 37 class AsyncOutlineClient :
+ 38 """
+ 39 Asynchronous client for the Outline VPN Server API.
+ 40
+ 41 Args:
+ 42 api_url: Base URL for the Outline server API
+ 43 cert_sha256: SHA-256 fingerprint of the server's TLS certificate
+ 44 json_format: Return raw JSON instead of Pydantic models
+ 45 timeout: Request timeout in seconds
+ 46
+ 47 Examples:
+ 48 >>> async def doo_something():
+ 49 ... async with AsyncOutlineClient(
+ 50 ... "https://example.com:1234/secret",
+ 51 ... "ab12cd34..."
+ 52 ... ) as client:
+ 53 ... server_info = await client.get_server_info()
+ 54 """
+ 55
+ 56 def __init__ (
+ 57 self ,
+ 58 api_url : str ,
+ 59 cert_sha256 : str ,
+ 60 * ,
+ 61 json_format : bool = True ,
+ 62 timeout : float = 30.0 ,
+ 63 ) -> None :
+ 64 self . _api_url = api_url . rstrip ( "/" )
+ 65 self . _cert_sha256 = cert_sha256
+ 66 self . _json_format = json_format
+ 67 self . _timeout = aiohttp . ClientTimeout ( total = timeout )
+ 68 self . _ssl_context = None
+ 69 self . _session : Optional [ aiohttp . ClientSession ] = None
+ 70 self . _in_context = False
+ 71
+ 72 async def __aenter__ ( self ) -> AsyncOutlineClient :
+ 73 """Set up client session for context manager."""
+ 74 self . _session = aiohttp . ClientSession (
+ 75 timeout = self . _timeout , raise_for_status = True
+ 76 )
+ 77 self . _in_context = True
+ 78 return self
+ 79
+ 80 async def __aexit__ ( self , exc_type : Any , exc_val : Any , exc_tb : Any ) -> None :
+ 81 """Clean up client session."""
+ 82 if self . _session :
+ 83 await self . _session . close ()
+ 84 self . _session = None
+ 85 self . _in_context = False
+ 86
+ 87 def _ensure_context ( self ):
+ 88 """Ensure the session context is valid."""
+ 89 if not self . _session or self . _session . closed :
+ 90 raise RuntimeError ( "Client session is not initialized or already closed." )
+ 91
+ 92 @overload
+ 93 async def _parse_response (
+ 94 self ,
+ 95 response : ClientResponse ,
+ 96 model : type [ BaseModel ],
+ 97 json_format : Literal [ True ],
+ 98 ) -> JsonDict : ...
+ 99
+100 @overload
+101 async def _parse_response (
+102 self ,
+103 response : ClientResponse ,
+104 model : type [ BaseModel ],
+105 json_format : Literal [ False ],
+106 ) -> BaseModel : ...
+107
+108 @overload
+109 async def _parse_response (
+110 self , response : ClientResponse , model : type [ BaseModel ], json_format : bool
+111 ) -> Union [ JsonDict , BaseModel ]: ...
+112
+113 async def _parse_response (
+114 self , response : ClientResponse , model : type [ BaseModel ], json_format : bool = True
+115 ) -> Union [ JsonDict , BaseModel ]:
+116 """
+117 Parse and validate API response data.
+118
+119 Args:
+120 response: API response to parse
+121 model: Pydantic model for validation
+122 json_format: Whether to return raw JSON
+123
+124 Returns:
+125 Validated response data
+126
+127 Raises:
+128 ValueError: If response validation fails
+129 """
+130 self . _ensure_context ()
+131
+132 try :
+133 data = await response . json ()
+134 except aiohttp . ContentTypeError :
+135 raise ValueError ( "Invalid response format" ) from None
+136 try :
+137 validated = model . model_validate ( data )
+138 return validated . model_dump () if json_format else validated
+139 except Exception as e :
+140 raise ValueError ( f "Value error: { e } " ) from e
+141
+142 @staticmethod
+143 async def _handle_error_response ( response : ClientResponse ) -> None :
+144 """Handle error responses from the API."""
+145 try :
+146 error_data = await response . json ()
+147 error = ErrorResponse . model_validate ( error_data )
+148 raise APIError ( f " { error . code } : { error . message } " )
+149 except ValueError :
+150 raise APIError ( f "HTTP { response . status } : { response . reason } " )
+151
+152 async def _request (
+153 self ,
+154 method : str ,
+155 endpoint : str ,
+156 * ,
+157 json : Any = None ,
+158 params : Optional [ dict [ str , Any ]] = None ,
+159 ) -> Any :
+160 """Make an API request."""
+161 self . _ensure_context ()
+162
+163 url = self . _build_url ( endpoint )
+164 ssl_context = self . _get_ssl_context ()
+165
+166 async with self . _session . request (
+167 method ,
+168 url ,
+169 json = json ,
+170 params = params ,
+171 ssl = ssl_context ,
+172 raise_for_status = False ,
+173 timeout = self . _timeout ,
+174 ) as response :
+175 if response . status >= 400 :
+176 await self . _handle_error_response ( response )
+177
+178 if response . status == 204 :
+179 return True # No content response
+180
+181 try :
+182 await response . json ()
+183 return response
+184 except aiohttp . ContentTypeError :
+185 return await response . text () # Fallback for non-JSON responses
+186 except Exception as e :
+187 raise APIError ( f "Failed to parse response from { url } : { e } " ) from e
+188
+189 def _build_url ( self , endpoint : str ) -> str :
+190 """Build and validate the full URL for the API request."""
+191 if not isinstance ( endpoint , str ):
+192 raise ValueError ( "Endpoint must be a string" )
+193
+194 endpoint = endpoint . lstrip ( "/" )
+195 url = f " { self . _api_url } / { endpoint } "
+196
+197 parsed_url = urlparse ( url )
+198 if not parsed_url . scheme or not parsed_url . netloc :
+199 raise ValueError ( f "Invalid URL: { url } " )
+200
+201 return url
+202
+203 def _get_ssl_context ( self ) -> Optional [ Fingerprint ]:
+204 """Create an SSL context if a certificate fingerprint is provided."""
+205 if not self . _cert_sha256 :
+206 return None
+207
+208 try :
+209 fingerprint = binascii . unhexlify ( self . _cert_sha256 )
+210 return Fingerprint ( fingerprint )
+211 except binascii . Error as e :
+212 raise ValueError ( f "Invalid certificate SHA256: { self . _cert_sha256 } " ) from e
+213 except Exception as e :
+214 raise OutlineError ( "Error while creating SSL context" ) from e
+215
+216 async def get_server_info ( self ) -> Union [ JsonDict , Server ]:
+217 """
+218 Get server information.
+219
+220 Returns:
+221 Server information including name, ID, and configuration.
+222
+223 Examples:
+224 >>> async def doo_something():
+225 ... async with AsyncOutlineClient(
+226 ... "https://example.com:1234/secret",
+227 ... "ab12cd34..."
+228 ... ) as client:
+229 ... server = await client.get_server_info()
+230 ... print(f"Server {server.name} running version {server.version}")
+231 """
+232 response = await self . _request ( "GET" , "server" )
+233 return await self . _parse_response (
+234 response , Server , json_format = self . _json_format
+235 )
+236
+237 async def rename_server ( self , name : str ) -> bool :
+238 """
+239 Rename the server.
+240
+241 Args:
+242 name: New server name
+243
+244 Returns:
+245 True if successful
+246
+247 Examples:
+248 >>> async def doo_something():
+249 ... async with AsyncOutlineClient(
+250 ... "https://example.com:1234/secret",
+251 ... "ab12cd34..."
+252 ... ) as client:
+253 ... success = await client.rename_server("My VPN Server")
+254 ... if success:
+255 ... print("Server renamed successfully")
+256 """
+257 return await self . _request ( "PUT" , "name" , json = { "name" : name })
+258
+259 async def set_hostname ( self , hostname : str ) -> bool :
+260 """
+261 Set server hostname for access keys.
+262
+263 Args:
+264 hostname: New hostname or IP address
+265
+266 Returns:
+267 True if successful
+268
+269 Raises:
+270 APIError: If hostname is invalid
+271
+272 Examples:
+273 >>> async def doo_something():
+274 ... async with AsyncOutlineClient(
+275 ... "https://example.com:1234/secret",
+276 ... "ab12cd34..."
+277 ... ) as client:
+278 ... await client.set_hostname("vpn.example.com")
+279 ... # Or use IP address
+280 ... await client.set_hostname("203.0.113.1")
+281 """
+282 return await self . _request (
+283 "PUT" , "server/hostname-for-access-keys" , json = { "hostname" : hostname }
+284 )
+285
+286 async def set_default_port ( self , port : int ) -> bool :
+287 """
+288 Set default port for new access keys.
+289
+290 Args:
+291 port: Port number (1025-65535)
+292
+293 Returns:
+294 True if successful
+295
+296 Raises:
+297 APIError: If port is invalid or in use
+298
+299 Examples:
+300 >>> async def doo_something():
+301 ... async with AsyncOutlineClient(
+302 ... "https://example.com:1234/secret",
+303 ... "ab12cd34..."
+304 ... ) as client:
+305 ... await client.set_default_port(8388)
+306
+307 """
+308 return await self . _request (
+309 "PUT" , "server/port-for-new-access-keys" , json = { "port" : port }
+310 )
+311
+312 async def get_metrics_status ( self ) -> dict [ str , Any ] | BaseModel :
+313 """
+314 Get whether metrics collection is enabled.
+315
+316 Returns:
+317 Current metrics collection status
+318
+319 Examples:
+320 >>> async def doo_something():
+321 ... async with AsyncOutlineClient(
+322 ... "https://example.com:1234/secret",
+323 ... "ab12cd34..."
+324 ... ) as client:
+325 ... if await client.get_metrics_status():
+326 ... print("Metrics collection is enabled")
+327 """
+328 response = await self . _request ( "GET" , "metrics/enabled" )
+329 data = await self . _parse_response (
+330 response , MetricsStatusResponse , json_format = self . _json_format
+331 )
+332 return data
+333
+334 async def set_metrics_status ( self , enabled : bool ) -> bool :
+335 """
+336 Enable or disable metrics collection.
+337
+338 Args:
+339 enabled: Whether to enable metrics
+340
+341 Returns:
+342 True if successful
+343
+344 Examples:
+345 >>> async def doo_something():
+346 ... async with AsyncOutlineClient(
+347 ... "https://example.com:1234/secret",
+348 ... "ab12cd34..."
+349 ... ) as client:
+350 ... # Enable metrics
+351 ... await client.set_metrics_status(True)
+352 ... # Check new status
+353 ... is_enabled = await client.get_metrics_status()
+354 """
+355 return await self . _request (
+356 "PUT" , "metrics/enabled" , json = { "metricsEnabled" : enabled }
+357 )
+358
+359 async def get_transfer_metrics (
+360 self , period : MetricsPeriod = MetricsPeriod . MONTHLY
+361 ) -> Union [ JsonDict , ServerMetrics ]:
+362 """
+363 Get transfer metrics for specified period.
+364
+365 Args:
+366 period: Time period for metrics (DAILY, WEEKLY, or MONTHLY)
+367
+368 Returns:
+369 Transfer metrics data for each access key
+370
+371 Examples:
+372 >>> async def doo_something():
+373 ... async with AsyncOutlineClient(
+374 ... "https://example.com:1234/secret",
+375 ... "ab12cd34..."
+376 ... ) as client:
+377 ... # Get monthly metrics
+378 ... metrics = await client.get_transfer_metrics()
+379 ... # Or get daily metrics
+380 ... daily = await client.get_transfer_metrics(MetricsPeriod.DAILY)
+381 ... for user_id, bytes_transferred in daily.bytes_transferred_by_user_id.items():
+382 ... print(f"User {user_id}: {bytes_transferred / 1024**3:.2f} GB")
+383 """
+384 response = await self . _request (
+385 "GET" , "metrics/transfer" , params = { "period" : period . value }
+386 )
+387 return await self . _parse_response (
+388 response , ServerMetrics , json_format = self . _json_format
+389 )
+390
+391 async def create_access_key (
+392 self ,
+393 * ,
+394 name : Optional [ str ] = None ,
+395 password : Optional [ str ] = None ,
+396 port : Optional [ int ] = None ,
+397 method : Optional [ str ] = None ,
+398 limit : Optional [ DataLimit ] = None ,
+399 ) -> Union [ JsonDict , AccessKey ]:
+400 """
+401 Create a new access key.
+402
+403 Args:
+404 name: Optional key name
+405 password: Optional password
+406 port: Optional port number (1-65535)
+407 method: Optional encryption method
+408 limit: Optional data transfer limit
+409
+410 Returns:
+411 New access key details
+412
+413 Examples:
+414 >>> async def doo_something():
+415 ... async with AsyncOutlineClient(
+416 ... "https://example.com:1234/secret",
+417 ... "ab12cd34..."
+418 ... ) as client:
+419 ... # Create basic key
+420 ... key = await client.create_access_key(name="User 1")
+421 ...
+422 ... # Create key with data limit
+423 ... _limit = DataLimit(bytes=5 * 1024**3) # 5 GB
+424 ... key = await client.create_access_key(
+425 ... name="Limited User",
+426 ... port=8388,
+427 ... limit=_limit
+428 ... )
+429 ... print(f"Created key: {key.access_url}")
+430 """
+431 request = AccessKeyCreateRequest (
+432 name = name , password = password , port = port , method = method , limit = limit
+433 )
+434 response = await self . _request (
+435 "POST" , "access-keys" , json = request . model_dump ( exclude_none = True )
+436 )
+437 return await self . _parse_response (
+438 response , AccessKey , json_format = self . _json_format
+439 )
+440
+441 async def get_access_keys ( self ) -> Union [ JsonDict , AccessKeyList ]:
+442 """
+443 Get all access keys.
+444
+445 Returns:
+446 List of all access keys
+447
+448 Examples:
+449 >>> async def doo_something():
+450 ... async with AsyncOutlineClient(
+451 ... "https://example.com:1234/secret",
+452 ... "ab12cd34..."
+453 ... ) as client:
+454 ... keys = await client.get_access_keys()
+455 ... for key in keys.access_keys:
+456 ... print(f"Key {key.id}: {key.name or 'unnamed'}")
+457 ... if key.data_limit:
+458 ... print(f" Limit: {key.data_limit.bytes / 1024**3:.1f} GB")
+459 """
+460 response = await self . _request ( "GET" , "access-keys" )
+461 return await self . _parse_response (
+462 response , AccessKeyList , json_format = self . _json_format
+463 )
+464
+465 async def get_access_key ( self , key_id : int ) -> Union [ JsonDict , AccessKey ]:
+466 """
+467 Get specific access key.
+468
+469 Args:
+470 key_id: Access key ID
+471
+472 Returns:
+473 Access key details
+474
+475 Raises:
+476 APIError: If key doesn't exist
+477
+478 Examples:
+479 >>> async def doo_something():
+480 ... async with AsyncOutlineClient(
+481 ... "https://example.com:1234/secret",
+482 ... "ab12cd34..."
+483 ... ) as client:
+484 ... key = await client.get_access_key(1)
+485 ... print(f"Port: {key.port}")
+486 ... print(f"URL: {key.access_url}")
+487 """
+488 response = await self . _request ( "GET" , f "access-keys/ { key_id } " )
+489 return await self . _parse_response (
+490 response , AccessKey , json_format = self . _json_format
+491 )
+492
+493 async def rename_access_key ( self , key_id : int , name : str ) -> bool :
+494 """
+495 Rename access key.
+496
+497 Args:
+498 key_id: Access key ID
+499 name: New name
+500
+501 Returns:
+502 True if successful
+503
+504 Raises:
+505 APIError: If key doesn't exist
+506
+507 Examples:
+508 >>> async def doo_something():
+509 ... async with AsyncOutlineClient(
+510 ... "https://example.com:1234/secret",
+511 ... "ab12cd34..."
+512 ... ) as client:
+513 ... # Rename key
+514 ... await client.rename_access_key(1, "Alice")
+515 ...
+516 ... # Verify new name
+517 ... key = await client.get_access_key(1)
+518 ... assert key.name == "Alice"
+519 """
+520 return await self . _request (
+521 "PUT" , f "access-keys/ { key_id } /name" , json = { "name" : name }
+522 )
+523
+524 async def delete_access_key ( self , key_id : int ) -> bool :
+525 """
+526 Delete access key.
+527
+528 Args:
+529 key_id: Access key ID
+530
+531 Returns:
+532 True if successful
+533
+534 Raises:
+535 APIError: If key doesn't exist
+536
+537 Examples:
+538 >>> async def doo_something():
+539 ... async with AsyncOutlineClient(
+540 ... "https://example.com:1234/secret",
+541 ... "ab12cd34..."
+542 ... ) as client:
+543 ... if await client.delete_access_key(1):
+544 ... print("Key deleted")
+545
+546 """
+547 return await self . _request ( "DELETE" , f "access-keys/ { key_id } " )
+548
+549 async def set_access_key_data_limit ( self , key_id : int , bytes_limit : int ) -> bool :
+550 """
+551 Set data transfer limit for access key.
+552
+553 Args:
+554 key_id: Access key ID
+555 bytes_limit: Limit in bytes (must be positive)
+556
+557 Returns:
+558 True if successful
+559
+560 Raises:
+561 APIError: If key doesn't exist or limit is invalid
+562
+563 Examples:
+564 >>> async def doo_something():
+565 ... async with AsyncOutlineClient(
+566 ... "https://example.com:1234/secret",
+567 ... "ab12cd34..."
+568 ... ) as client:
+569 ... # Set 5 GB limit
+570 ... limit = 5 * 1024**3 # 5 GB in bytes
+571 ... await client.set_access_key_data_limit(1, limit)
+572 ...
+573 ... # Verify limit
+574 ... key = await client.get_access_key(1)
+575 ... assert key.data_limit and key.data_limit.bytes == limit
+576 """
+577 return await self . _request (
+578 "PUT" ,
+579 f "access-keys/ { key_id } /data-limit" ,
+580 json = { "limit" : { "bytes" : bytes_limit }},
+581 )
+582
+583 async def remove_access_key_data_limit ( self , key_id : str ) -> bool :
+584 """
+585 Remove data transfer limit from access key.
+586
+587 Args:
+588 key_id: Access key ID
+589
+590 Returns:
+591 True if successful
+592
+593 Raises:
+594 APIError: If key doesn't exist
+595 """
+596 return await self . _request ( "DELETE" , f "access-keys/ { key_id } /data-limit" )
+
+
+
+ Asynchronous client for the Outline VPN Server API.
+
+
Arguments:
+
+
+api_url: Base URL for the Outline server API
+cert_sha256: SHA-256 fingerprint of the server's TLS certificate
+json_format: Return raw JSON instead of Pydantic models
+timeout: Request timeout in seconds
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... server_info = await client . get_server_info ()
+
+
+
+
+
+
+
+
+
+
+ AsyncOutlineClient ( api_url : str , cert_sha256 : str , * , json_format : bool = True , timeout : float = 30.0 )
+
+ View Source
+
+
+
+
56 def __init__ (
+57 self ,
+58 api_url : str ,
+59 cert_sha256 : str ,
+60 * ,
+61 json_format : bool = True ,
+62 timeout : float = 30.0 ,
+63 ) -> None :
+64 self . _api_url = api_url . rstrip ( "/" )
+65 self . _cert_sha256 = cert_sha256
+66 self . _json_format = json_format
+67 self . _timeout = aiohttp . ClientTimeout ( total = timeout )
+68 self . _ssl_context = None
+69 self . _session : Optional [ aiohttp . ClientSession ] = None
+70 self . _in_context = False
+
+
+
+
+
+
+
+
+
+
+
async def
+
get_server_info (self ) -> Union [ dict [ str , Any ], Server ] :
+
+
View Source
+
+
+
+
216 async def get_server_info ( self ) -> Union [ JsonDict , Server ]:
+217 """
+218 Get server information.
+219
+220 Returns:
+221 Server information including name, ID, and configuration.
+222
+223 Examples:
+224 >>> async def doo_something():
+225 ... async with AsyncOutlineClient(
+226 ... "https://example.com:1234/secret",
+227 ... "ab12cd34..."
+228 ... ) as client:
+229 ... server = await client.get_server_info()
+230 ... print(f"Server {server.name} running version {server.version}")
+231 """
+232 response = await self . _request ( "GET" , "server" )
+233 return await self . _parse_response (
+234 response , Server , json_format = self . _json_format
+235 )
+
+
+
+
Get server information.
+
+
Returns:
+
+
+ Server information including name, ID, and configuration.
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... server = await client . get_server_info ()
+... print ( f "Server { server . name } running version { server . version } " )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ rename_server (self , name : str ) -> bool :
+
+ View Source
+
+
+
+
237 async def rename_server ( self , name : str ) -> bool :
+238 """
+239 Rename the server.
+240
+241 Args:
+242 name: New server name
+243
+244 Returns:
+245 True if successful
+246
+247 Examples:
+248 >>> async def doo_something():
+249 ... async with AsyncOutlineClient(
+250 ... "https://example.com:1234/secret",
+251 ... "ab12cd34..."
+252 ... ) as client:
+253 ... success = await client.rename_server("My VPN Server")
+254 ... if success:
+255 ... print("Server renamed successfully")
+256 """
+257 return await self . _request ( "PUT" , "name" , json = { "name" : name })
+
+
+
+
Rename the server.
+
+
Arguments:
+
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... success = await client . rename_server ( "My VPN Server" )
+... if success :
+... print ( "Server renamed successfully" )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ set_hostname (self , hostname : str ) -> bool :
+
+ View Source
+
+
+
+
259 async def set_hostname ( self , hostname : str ) -> bool :
+260 """
+261 Set server hostname for access keys.
+262
+263 Args:
+264 hostname: New hostname or IP address
+265
+266 Returns:
+267 True if successful
+268
+269 Raises:
+270 APIError: If hostname is invalid
+271
+272 Examples:
+273 >>> async def doo_something():
+274 ... async with AsyncOutlineClient(
+275 ... "https://example.com:1234/secret",
+276 ... "ab12cd34..."
+277 ... ) as client:
+278 ... await client.set_hostname("vpn.example.com")
+279 ... # Or use IP address
+280 ... await client.set_hostname("203.0.113.1")
+281 """
+282 return await self . _request (
+283 "PUT" , "server/hostname-for-access-keys" , json = { "hostname" : hostname }
+284 )
+
+
+
+
Set server hostname for access keys.
+
+
Arguments:
+
+
+hostname: New hostname or IP address
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If hostname is invalid
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... await client . set_hostname ( "vpn.example.com" )
+... # Or use IP address
+... await client . set_hostname ( "203.0.113.1" )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ set_default_port (self , port : int ) -> bool :
+
+ View Source
+
+
+
+
286 async def set_default_port ( self , port : int ) -> bool :
+287 """
+288 Set default port for new access keys.
+289
+290 Args:
+291 port: Port number (1025-65535)
+292
+293 Returns:
+294 True if successful
+295
+296 Raises:
+297 APIError: If port is invalid or in use
+298
+299 Examples:
+300 >>> async def doo_something():
+301 ... async with AsyncOutlineClient(
+302 ... "https://example.com:1234/secret",
+303 ... "ab12cd34..."
+304 ... ) as client:
+305 ... await client.set_default_port(8388)
+306
+307 """
+308 return await self . _request (
+309 "PUT" , "server/port-for-new-access-keys" , json = { "port" : port }
+310 )
+
+
+
+
Set default port for new access keys.
+
+
Arguments:
+
+
+port: Port number (1025-65535)
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If port is invalid or in use
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... await client . set_default_port ( 8388 )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ get_metrics_status (self ) -> dict [ str , typing . Any ] | pydantic . main . BaseModel :
+
+ View Source
+
+
+
+
312 async def get_metrics_status ( self ) -> dict [ str , Any ] | BaseModel :
+313 """
+314 Get whether metrics collection is enabled.
+315
+316 Returns:
+317 Current metrics collection status
+318
+319 Examples:
+320 >>> async def doo_something():
+321 ... async with AsyncOutlineClient(
+322 ... "https://example.com:1234/secret",
+323 ... "ab12cd34..."
+324 ... ) as client:
+325 ... if await client.get_metrics_status():
+326 ... print("Metrics collection is enabled")
+327 """
+328 response = await self . _request ( "GET" , "metrics/enabled" )
+329 data = await self . _parse_response (
+330 response , MetricsStatusResponse , json_format = self . _json_format
+331 )
+332 return data
+
+
+
+
Get whether metrics collection is enabled.
+
+
Returns:
+
+
+ Current metrics collection status
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... if await client . get_metrics_status ():
+... print ( "Metrics collection is enabled" )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ set_metrics_status (self , enabled : bool ) -> bool :
+
+ View Source
+
+
+
+
334 async def set_metrics_status ( self , enabled : bool ) -> bool :
+335 """
+336 Enable or disable metrics collection.
+337
+338 Args:
+339 enabled: Whether to enable metrics
+340
+341 Returns:
+342 True if successful
+343
+344 Examples:
+345 >>> async def doo_something():
+346 ... async with AsyncOutlineClient(
+347 ... "https://example.com:1234/secret",
+348 ... "ab12cd34..."
+349 ... ) as client:
+350 ... # Enable metrics
+351 ... await client.set_metrics_status(True)
+352 ... # Check new status
+353 ... is_enabled = await client.get_metrics_status()
+354 """
+355 return await self . _request (
+356 "PUT" , "metrics/enabled" , json = { "metricsEnabled" : enabled }
+357 )
+
+
+
+
Enable or disable metrics collection.
+
+
Arguments:
+
+
+enabled: Whether to enable metrics
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... # Enable metrics
+... await client . set_metrics_status ( True )
+... # Check new status
+... is_enabled = await client . get_metrics_status ()
+
+
+
+
+
+
+
+
+
+
+
+
359 async def get_transfer_metrics (
+360 self , period : MetricsPeriod = MetricsPeriod . MONTHLY
+361 ) -> Union [ JsonDict , ServerMetrics ]:
+362 """
+363 Get transfer metrics for specified period.
+364
+365 Args:
+366 period: Time period for metrics (DAILY, WEEKLY, or MONTHLY)
+367
+368 Returns:
+369 Transfer metrics data for each access key
+370
+371 Examples:
+372 >>> async def doo_something():
+373 ... async with AsyncOutlineClient(
+374 ... "https://example.com:1234/secret",
+375 ... "ab12cd34..."
+376 ... ) as client:
+377 ... # Get monthly metrics
+378 ... metrics = await client.get_transfer_metrics()
+379 ... # Or get daily metrics
+380 ... daily = await client.get_transfer_metrics(MetricsPeriod.DAILY)
+381 ... for user_id, bytes_transferred in daily.bytes_transferred_by_user_id.items():
+382 ... print(f"User {user_id}: {bytes_transferred / 1024**3:.2f} GB")
+383 """
+384 response = await self . _request (
+385 "GET" , "metrics/transfer" , params = { "period" : period . value }
+386 )
+387 return await self . _parse_response (
+388 response , ServerMetrics , json_format = self . _json_format
+389 )
+
+
+
+
Get transfer metrics for specified period.
+
+
Arguments:
+
+
+period: Time period for metrics (DAILY, WEEKLY, or MONTHLY)
+
+
+
Returns:
+
+
+ Transfer metrics data for each access key
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... # Get monthly metrics
+... metrics = await client . get_transfer_metrics ()
+... # Or get daily metrics
+... daily = await client . get_transfer_metrics ( MetricsPeriod.DAILY )
+... for user_id , bytes_transferred in daily . bytes_transferred_by_user_id . items ():
+... print ( f "User { user_id } : { bytes_transferred / 1024 ** 3 : .2f } GB" )
+
+
+
+
+
+
+
+
+
+
+
+
async def
+
create_access_key ( self , * , name : Optional [ str ] = None , password : Optional [ str ] = None , port : Optional [ int ] = None , method : Optional [ str ] = None , limit : Optional [ DataLimit ] = None ) -> Union [ dict [ str , Any ], AccessKey ] :
+
+
View Source
+
+
+
+
391 async def create_access_key (
+392 self ,
+393 * ,
+394 name : Optional [ str ] = None ,
+395 password : Optional [ str ] = None ,
+396 port : Optional [ int ] = None ,
+397 method : Optional [ str ] = None ,
+398 limit : Optional [ DataLimit ] = None ,
+399 ) -> Union [ JsonDict , AccessKey ]:
+400 """
+401 Create a new access key.
+402
+403 Args:
+404 name: Optional key name
+405 password: Optional password
+406 port: Optional port number (1-65535)
+407 method: Optional encryption method
+408 limit: Optional data transfer limit
+409
+410 Returns:
+411 New access key details
+412
+413 Examples:
+414 >>> async def doo_something():
+415 ... async with AsyncOutlineClient(
+416 ... "https://example.com:1234/secret",
+417 ... "ab12cd34..."
+418 ... ) as client:
+419 ... # Create basic key
+420 ... key = await client.create_access_key(name="User 1")
+421 ...
+422 ... # Create key with data limit
+423 ... _limit = DataLimit(bytes=5 * 1024**3) # 5 GB
+424 ... key = await client.create_access_key(
+425 ... name="Limited User",
+426 ... port=8388,
+427 ... limit=_limit
+428 ... )
+429 ... print(f"Created key: {key.access_url}")
+430 """
+431 request = AccessKeyCreateRequest (
+432 name = name , password = password , port = port , method = method , limit = limit
+433 )
+434 response = await self . _request (
+435 "POST" , "access-keys" , json = request . model_dump ( exclude_none = True )
+436 )
+437 return await self . _parse_response (
+438 response , AccessKey , json_format = self . _json_format
+439 )
+
+
+
+
Create a new access key.
+
+
Arguments:
+
+
+name: Optional key name
+password: Optional password
+port: Optional port number (1-65535)
+method: Optional encryption method
+limit: Optional data transfer limit
+
+
+
Returns:
+
+
+ New access key details
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... # Create basic key
+... key = await client . create_access_key ( name = "User 1" )
+...
+... # Create key with data limit
+... _limit = DataLimit ( bytes = 5 * 1024 ** 3 ) # 5 GB
+... key = await client . create_access_key (
+... name = "Limited User" ,
+... port = 8388 ,
+... limit = _limit
+... )
+... print ( f "Created key: { key . access_url } " )
+
+
+
+
+
+
+
+
+
+
+
+
async def
+
get_access_keys (self ) -> Union [ dict [ str , Any ], AccessKeyList ] :
+
+
View Source
+
+
+
+
441 async def get_access_keys ( self ) -> Union [ JsonDict , AccessKeyList ]:
+442 """
+443 Get all access keys.
+444
+445 Returns:
+446 List of all access keys
+447
+448 Examples:
+449 >>> async def doo_something():
+450 ... async with AsyncOutlineClient(
+451 ... "https://example.com:1234/secret",
+452 ... "ab12cd34..."
+453 ... ) as client:
+454 ... keys = await client.get_access_keys()
+455 ... for key in keys.access_keys:
+456 ... print(f"Key {key.id}: {key.name or 'unnamed'}")
+457 ... if key.data_limit:
+458 ... print(f" Limit: {key.data_limit.bytes / 1024**3:.1f} GB")
+459 """
+460 response = await self . _request ( "GET" , "access-keys" )
+461 return await self . _parse_response (
+462 response , AccessKeyList , json_format = self . _json_format
+463 )
+
+
+
+
Get all access keys.
+
+
Returns:
+
+
+ List of all access keys
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... keys = await client . get_access_keys ()
+... for key in keys . access_keys :
+... print ( f "Key { key . id } : { key . name or 'unnamed' } " )
+... if key . data_limit :
+... print ( f " Limit: { key . data_limit . bytes / 1024 ** 3 : .1f } GB" )
+
+
+
+
+
+
+
+
+
+
+
+
async def
+
get_access_key ( self , key_id : int ) -> Union [ dict [ str , Any ], AccessKey ] :
+
+
View Source
+
+
+
+
465 async def get_access_key ( self , key_id : int ) -> Union [ JsonDict , AccessKey ]:
+466 """
+467 Get specific access key.
+468
+469 Args:
+470 key_id: Access key ID
+471
+472 Returns:
+473 Access key details
+474
+475 Raises:
+476 APIError: If key doesn't exist
+477
+478 Examples:
+479 >>> async def doo_something():
+480 ... async with AsyncOutlineClient(
+481 ... "https://example.com:1234/secret",
+482 ... "ab12cd34..."
+483 ... ) as client:
+484 ... key = await client.get_access_key(1)
+485 ... print(f"Port: {key.port}")
+486 ... print(f"URL: {key.access_url}")
+487 """
+488 response = await self . _request ( "GET" , f "access-keys/ { key_id } " )
+489 return await self . _parse_response (
+490 response , AccessKey , json_format = self . _json_format
+491 )
+
+
+
+
Get specific access key.
+
+
Arguments:
+
+
+
+
Returns:
+
+
+ Access key details
+
+
+
Raises:
+
+
+APIError: If key doesn't exist
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... key = await client . get_access_key ( 1 )
+... print ( f "Port: { key . port } " )
+... print ( f "URL: { key . access_url } " )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ rename_access_key (self , key_id : int , name : str ) -> bool :
+
+ View Source
+
+
+
+
493 async def rename_access_key ( self , key_id : int , name : str ) -> bool :
+494 """
+495 Rename access key.
+496
+497 Args:
+498 key_id: Access key ID
+499 name: New name
+500
+501 Returns:
+502 True if successful
+503
+504 Raises:
+505 APIError: If key doesn't exist
+506
+507 Examples:
+508 >>> async def doo_something():
+509 ... async with AsyncOutlineClient(
+510 ... "https://example.com:1234/secret",
+511 ... "ab12cd34..."
+512 ... ) as client:
+513 ... # Rename key
+514 ... await client.rename_access_key(1, "Alice")
+515 ...
+516 ... # Verify new name
+517 ... key = await client.get_access_key(1)
+518 ... assert key.name == "Alice"
+519 """
+520 return await self . _request (
+521 "PUT" , f "access-keys/ { key_id } /name" , json = { "name" : name }
+522 )
+
+
+
+
Rename access key.
+
+
Arguments:
+
+
+key_id: Access key ID
+name: New name
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If key doesn't exist
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... # Rename key
+... await client . rename_access_key ( 1 , "Alice" )
+...
+... # Verify new name
+... key = await client . get_access_key ( 1 )
+... assert key . name == "Alice"
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ delete_access_key (self , key_id : int ) -> bool :
+
+ View Source
+
+
+
+
524 async def delete_access_key ( self , key_id : int ) -> bool :
+525 """
+526 Delete access key.
+527
+528 Args:
+529 key_id: Access key ID
+530
+531 Returns:
+532 True if successful
+533
+534 Raises:
+535 APIError: If key doesn't exist
+536
+537 Examples:
+538 >>> async def doo_something():
+539 ... async with AsyncOutlineClient(
+540 ... "https://example.com:1234/secret",
+541 ... "ab12cd34..."
+542 ... ) as client:
+543 ... if await client.delete_access_key(1):
+544 ... print("Key deleted")
+545
+546 """
+547 return await self . _request ( "DELETE" , f "access-keys/ { key_id } " )
+
+
+
+
Delete access key.
+
+
Arguments:
+
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If key doesn't exist
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... if await client . delete_access_key ( 1 ):
+... print ( "Key deleted" )
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ set_access_key_data_limit (self , key_id : int , bytes_limit : int ) -> bool :
+
+ View Source
+
+
+
+
549 async def set_access_key_data_limit ( self , key_id : int , bytes_limit : int ) -> bool :
+550 """
+551 Set data transfer limit for access key.
+552
+553 Args:
+554 key_id: Access key ID
+555 bytes_limit: Limit in bytes (must be positive)
+556
+557 Returns:
+558 True if successful
+559
+560 Raises:
+561 APIError: If key doesn't exist or limit is invalid
+562
+563 Examples:
+564 >>> async def doo_something():
+565 ... async with AsyncOutlineClient(
+566 ... "https://example.com:1234/secret",
+567 ... "ab12cd34..."
+568 ... ) as client:
+569 ... # Set 5 GB limit
+570 ... limit = 5 * 1024**3 # 5 GB in bytes
+571 ... await client.set_access_key_data_limit(1, limit)
+572 ...
+573 ... # Verify limit
+574 ... key = await client.get_access_key(1)
+575 ... assert key.data_limit and key.data_limit.bytes == limit
+576 """
+577 return await self . _request (
+578 "PUT" ,
+579 f "access-keys/ { key_id } /data-limit" ,
+580 json = { "limit" : { "bytes" : bytes_limit }},
+581 )
+
+
+
+
Set data transfer limit for access key.
+
+
Arguments:
+
+
+key_id: Access key ID
+bytes_limit: Limit in bytes (must be positive)
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If key doesn't exist or limit is invalid
+
+
+
Examples:
+
+
+
+
>>> async def doo_something ():
+... async with AsyncOutlineClient (
+... "https://example.com:1234/secret" ,
+... "ab12cd34..."
+... ) as client :
+... # Set 5 GB limit
+... limit = 5 * 1024 ** 3 # 5 GB in bytes
+... await client . set_access_key_data_limit ( 1 , limit )
+...
+... # Verify limit
+... key = await client . get_access_key ( 1 )
+... assert key . data_limit and key . data_limit . bytes == limit
+
+
+
+
+
+
+
+
+
+
+
+ async def
+ remove_access_key_data_limit (self , key_id : str ) -> bool :
+
+ View Source
+
+
+
+
583 async def remove_access_key_data_limit ( self , key_id : str ) -> bool :
+584 """
+585 Remove data transfer limit from access key.
+586
+587 Args:
+588 key_id: Access key ID
+589
+590 Returns:
+591 True if successful
+592
+593 Raises:
+594 APIError: If key doesn't exist
+595 """
+596 return await self . _request ( "DELETE" , f "access-keys/ { key_id } /data-limit" )
+
+
+
+
Remove data transfer limit from access key.
+
+
Arguments:
+
+
+
+
Returns:
+
+
+ True if successful
+
+
+
Raises:
+
+
+APIError: If key doesn't exist
+
+
+
+
+
+
+
+
+
+
+ class
+ OutlineError (builtins.Exception ):
+
+ View Source
+
+
+
+ 25 class OutlineError ( Exception ):
+26 """Base exception for Outline client errors."""
+
+
+
+ Base exception for Outline client errors.
+
+
+
+
+
+
+
+
+
+ class
+ AccessKey (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 28 class AccessKey ( BaseModel ):
+29 """Access key details."""
+30
+31 id : int
+32 name : Optional [ str ] = None
+33 password : str
+34 port : int = Field ( gt = 0 , lt = 65536 )
+35 method : str
+36 access_url : str = Field ( alias = "accessUrl" )
+37 data_limit : Optional [ DataLimit ] = Field ( None , alias = "dataLimit" )
+
+
+
+
+
+
+
+
+ id : int
+
+
+
+
+
+
+
+
+
+
+ name : Optional[str]
+
+
+
+
+
+
+
+
+
+
+ password : str
+
+
+
+
+
+
+
+
+
+
+ port : int
+
+
+
+
+
+
+
+
+
+
+ method : str
+
+
+
+
+
+
+
+
+
+
+ access_url : str
+
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ AccessKeyCreateRequest (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 105 class AccessKeyCreateRequest ( BaseModel ):
+106 """
+107 Request parameters for creating an access key.
+108 Per OpenAPI: /access-keys POST request body
+109 """
+110
+111 name : Optional [ str ] = None
+112 method : Optional [ str ] = None
+113 password : Optional [ str ] = None
+114 port : Optional [ int ] = Field ( None , gt = 0 , lt = 65536 )
+115 limit : Optional [ DataLimit ] = None
+
+
+
+ Request parameters for creating an access key.
+Per OpenAPI: /access-keys POST request body
+
+
+
+
+
+ name : Optional[str]
+
+
+
+
+
+
+
+
+
+
+ method : Optional[str]
+
+
+
+
+
+
+
+
+
+
+ password : Optional[str]
+
+
+
+
+
+
+
+
+
+
+ port : Optional[int]
+
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ AccessKeyList (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 40 class AccessKeyList ( BaseModel ):
+41 """List of access keys."""
+42
+43 access_keys : list [ AccessKey ] = Field ( alias = "accessKeys" )
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ DataLimit (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 16 class DataLimit ( BaseModel ):
+17 """Data transfer limit configuration."""
+18
+19 bytes : int = Field ( gt = 0 )
+20
+21 @field_validator ( "bytes" )
+22 def validate_bytes ( cls , v : int ) -> int :
+23 if v < 0 :
+24 raise ValueError ( "bytes must be positive" )
+25 return v
+
+
+
+ Data transfer limit configuration.
+
+
+
+
+
+ bytes : int
+
+
+
+
+
+
+
+
+
+
+
+
@field_validator('bytes')
+
+
def
+
validate_bytes (cls , v : int ) -> int :
+
+
View Source
+
+
+
+
21 @field_validator ( "bytes" )
+22 def validate_bytes ( cls , v : int ) -> int :
+23 if v < 0 :
+24 raise ValueError ( "bytes must be positive" )
+25 return v
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ ErrorResponse (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 124 class ErrorResponse ( BaseModel ):
+125 """
+126 Error response structure
+127 Per OpenAPI: 404 and 400 responses
+128 """
+129
+130 code : str
+131 message : str
+
+
+
+ Error response structure
+Per OpenAPI: 404 and 400 responses
+
+
+
+
+
+ code : str
+
+
+
+
+
+
+
+
+
+
+ message : str
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ ExperimentalMetrics (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 79 class ExperimentalMetrics ( BaseModel ):
+80 """
+81 Experimental metrics data structure
+82 Per OpenAPI: /experimental/server/metrics endpoint
+83 """
+84
+85 server : list [ ServerMetric ]
+86 access_keys : list [ AccessKeyMetric ] = Field ( alias = "accessKeys" )
+
+
+
+ Experimental metrics data structure
+Per OpenAPI: /experimental/server/metrics endpoint
+
+
+
+
+
+ server : list[pyoutlineapi.models.ServerMetric]
+
+
+
+
+
+
+
+
+
+
+ access_keys : list[pyoutlineapi.models.AccessKeyMetric]
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ MetricsPeriod (builtins.str , enum.Enum ):
+
+ View Source
+
+
+
+ 8 class MetricsPeriod ( str , Enum ):
+ 9 """Time periods for metrics collection."""
+10
+11 DAILY = "daily"
+12 WEEKLY = "weekly"
+13 MONTHLY = "monthly"
+
+
+
+ Time periods for metrics collection.
+
+
+
+
+
+
+
+
+
+
+
+ class
+ MetricsStatusResponse (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 118 class MetricsStatusResponse ( BaseModel ):
+119 """Response for /metrics/enabled endpoint"""
+120
+121 metrics_enabled : bool = Field ( alias = "metricsEnabled" )
+
+
+
+ Response for /metrics/enabled endpoint
+
+
+
+
+
+ metrics_enabled : bool
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ Server (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 89 class Server ( BaseModel ):
+ 90 """
+ 91 Server information.
+ 92 Per OpenAPI: /server endpoint schema
+ 93 """
+ 94
+ 95 name : str
+ 96 server_id : str = Field ( alias = "serverId" )
+ 97 metrics_enabled : bool = Field ( alias = "metricsEnabled" )
+ 98 created_timestamp_ms : int = Field ( alias = "createdTimestampMs" )
+ 99 version : str
+100 port_for_new_access_keys : int = Field ( alias = "portForNewAccessKeys" , gt = 0 , lt = 65536 )
+101 hostname_for_access_keys : Optional [ str ] = Field ( None , alias = "hostnameForAccessKeys" )
+102 access_key_data_limit : Optional [ DataLimit ] = Field ( None , alias = "accessKeyDataLimit" )
+
+
+
+ Server information.
+Per OpenAPI: /server endpoint schema
+
+
+
+
+
+ name : str
+
+
+
+
+
+
+
+
+
+
+ server_id : str
+
+
+
+
+
+
+
+
+
+
+ metrics_enabled : bool
+
+
+
+
+
+
+
+
+
+
+ created_timestamp_ms : int
+
+
+
+
+
+
+
+
+
+
+ version : str
+
+
+
+
+
+
+
+
+
+
+ port_for_new_access_keys : int
+
+
+
+
+
+
+
+
+
+
+ hostname_for_access_keys : Optional[str]
+
+
+
+
+
+
+
+
+
+
+
access_key_data_limit : Optional[DataLimit ]
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
+
+ class
+ ServerMetrics (pydantic.main.BaseModel ):
+
+ View Source
+
+
+
+ 46 class ServerMetrics ( BaseModel ):
+47 """
+48 Server metrics data for data transferred per access key
+49 Per OpenAPI: /metrics/transfer endpoint
+50 """
+51
+52 bytes_transferred_by_user_id : dict [ str , int ] = Field (
+53 alias = "bytesTransferredByUserId"
+54 )
+
+
+
+ Server metrics data for data transferred per access key
+Per OpenAPI: /metrics/transfer endpoint
+
+
+
+
+
+ bytes_transferred_by_user_id : dict[str, int]
+
+
+
+
+
+
+
+
+
+
+ model_config : ClassVar[pydantic.config.ConfigDict] =
+{}
+
+
+
+
+
+
Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/search.js b/docs/search.js
new file mode 100644
index 0000000..6fbb620
--- /dev/null
+++ b/docs/search.js
@@ -0,0 +1,46 @@
+window.pdocSearch = (function(){
+/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o
\n"}, {"fullname": "pyoutlineapi.AsyncOutlineClient", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient", "kind": "class", "doc": "Asynchronous client for the Outline VPN Server API.
\n\nArguments: \n\n\napi_url: Base URL for the Outline server API \ncert_sha256: SHA-256 fingerprint of the server's TLS certificate \njson_format: Return raw JSON instead of Pydantic models \ntimeout: Request timeout in seconds \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... server_info = await client . get_server_info () \n
\n
\n \n"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.__init__", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.__init__", "kind": "function", "doc": "
\n", "signature": "(\tapi_url : str , \tcert_sha256 : str , \t* , \tjson_format : bool = True , \ttimeout : float = 30.0 ) "}, {"fullname": "pyoutlineapi.AsyncOutlineClient.get_server_info", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.get_server_info", "kind": "function", "doc": "Get server information.
\n\nReturns: \n\n\n Server information including name, ID, and configuration.
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... server = await client . get_server_info () \n... print ( f "Server { server . name } running version { server . version } " ) \n
\n
\n \n", "signature": "(self ) -> Union [ dict [ str , Any ], pyoutlineapi . models . Server ] : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.rename_server", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.rename_server", "kind": "function", "doc": "Rename the server.
\n\nArguments: \n\n\nname: New server name \n \n\nReturns: \n\n\n True if successful
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... success = await client . rename_server ( "My VPN Server" ) \n... if success : \n... print ( "Server renamed successfully" ) \n
\n
\n \n", "signature": "(self , name : str ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.set_hostname", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.set_hostname", "kind": "function", "doc": "Set server hostname for access keys.
\n\nArguments: \n\n\nhostname: New hostname or IP address \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If hostname is invalid \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... await client . set_hostname ( "vpn.example.com" ) \n... # Or use IP address \n... await client . set_hostname ( "203.0.113.1" ) \n
\n
\n \n", "signature": "(self , hostname : str ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.set_default_port", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.set_default_port", "kind": "function", "doc": "Set default port for new access keys.
\n\nArguments: \n\n\nport: Port number (1025-65535) \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If port is invalid or in use \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... await client . set_default_port ( 8388 ) \n
\n
\n \n", "signature": "(self , port : int ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.get_metrics_status", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.get_metrics_status", "kind": "function", "doc": "Get whether metrics collection is enabled.
\n\nReturns: \n\n\n Current metrics collection status
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... if await client . get_metrics_status (): \n... print ( "Metrics collection is enabled" ) \n
\n
\n \n", "signature": "(self ) -> dict [ str , typing . Any ] | pydantic . main . BaseModel : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.set_metrics_status", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.set_metrics_status", "kind": "function", "doc": "Enable or disable metrics collection.
\n\nArguments: \n\n\nenabled: Whether to enable metrics \n \n\nReturns: \n\n\n True if successful
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... # Enable metrics \n... await client . set_metrics_status ( True ) \n... # Check new status \n... is_enabled = await client . get_metrics_status () \n
\n
\n \n", "signature": "(self , enabled : bool ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.get_transfer_metrics", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.get_transfer_metrics", "kind": "function", "doc": "Get transfer metrics for specified period.
\n\nArguments: \n\n\nperiod: Time period for metrics (DAILY, WEEKLY, or MONTHLY) \n \n\nReturns: \n\n\n Transfer metrics data for each access key
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... # Get monthly metrics \n... metrics = await client . get_transfer_metrics () \n... # Or get daily metrics \n... daily = await client . get_transfer_metrics ( MetricsPeriod . DAILY ) \n... for user_id , bytes_transferred in daily . bytes_transferred_by_user_id . items (): \n... print ( f "User { user_id } : { bytes_transferred / 1024 ** 3 : .2f } GB" ) \n
\n
\n \n", "signature": "(\tself , \tperiod : pyoutlineapi . models . MetricsPeriod = < MetricsPeriod . MONTHLY : 'monthly' > ) -> Union [ dict [ str , Any ], pyoutlineapi . models . ServerMetrics ] : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.create_access_key", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.create_access_key", "kind": "function", "doc": "Create a new access key.
\n\nArguments: \n\n\nname: Optional key name \npassword: Optional password \nport: Optional port number (1-65535) \nmethod: Optional encryption method \nlimit: Optional data transfer limit \n \n\nReturns: \n\n\n New access key details
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... # Create basic key \n... key = await client . create_access_key ( name = "User 1" ) \n... \n... # Create key with data limit \n... _limit = DataLimit ( bytes = 5 * 1024 ** 3 ) # 5 GB \n... key = await client . create_access_key ( \n... name = "Limited User" , \n... port = 8388 , \n... limit = _limit \n... ) \n... print ( f "Created key: { key . access_url } " ) \n
\n
\n \n", "signature": "(\tself , \t* , \tname : Optional [ str ] = None , \tpassword : Optional [ str ] = None , \tport : Optional [ int ] = None , \tmethod : Optional [ str ] = None , \tlimit : Optional [ pyoutlineapi . models . DataLimit ] = None ) -> Union [ dict [ str , Any ], pyoutlineapi . models . AccessKey ] : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.get_access_keys", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.get_access_keys", "kind": "function", "doc": "Get all access keys.
\n\nReturns: \n\n\n List of all access keys
\n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... keys = await client . get_access_keys () \n... for key in keys . access_keys : \n... print ( f "Key { key . id } : { key . name or 'unnamed' } " ) \n... if key . data_limit : \n... print ( f " Limit: { key . data_limit . bytes / 1024 ** 3 : .1f } GB" ) \n
\n
\n \n", "signature": "(self ) -> Union [ dict [ str , Any ], pyoutlineapi . models . AccessKeyList ] : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.get_access_key", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.get_access_key", "kind": "function", "doc": "Get specific access key.
\n\nArguments: \n\n\nkey_id: Access key ID \n \n\nReturns: \n\n\n Access key details
\n \n\nRaises: \n\n\nAPIError: If key doesn't exist \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... key = await client . get_access_key ( 1 ) \n... print ( f "Port: { key . port } " ) \n... print ( f "URL: { key . access_url } " ) \n
\n
\n \n", "signature": "(\tself , \tkey_id : int ) -> Union [ dict [ str , Any ], pyoutlineapi . models . AccessKey ] : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.rename_access_key", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.rename_access_key", "kind": "function", "doc": "Rename access key.
\n\nArguments: \n\n\nkey_id: Access key ID \nname: New name \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If key doesn't exist \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... # Rename key \n... await client . rename_access_key ( 1 , "Alice" ) \n... \n... # Verify new name \n... key = await client . get_access_key ( 1 ) \n... assert key . name == "Alice" \n
\n
\n \n", "signature": "(self , key_id : int , name : str ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.delete_access_key", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.delete_access_key", "kind": "function", "doc": "Delete access key.
\n\nArguments: \n\n\nkey_id: Access key ID \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If key doesn't exist \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... if await client . delete_access_key ( 1 ): \n... print ( "Key deleted" ) \n
\n
\n \n", "signature": "(self , key_id : int ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.set_access_key_data_limit", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.set_access_key_data_limit", "kind": "function", "doc": "Set data transfer limit for access key.
\n\nArguments: \n\n\nkey_id: Access key ID \nbytes_limit: Limit in bytes (must be positive) \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If key doesn't exist or limit is invalid \n \n\nExamples: \n\n\n \n
>>> async def doo_something (): \n... async with AsyncOutlineClient ( \n... "https://example.com:1234/secret" , \n... "ab12cd34..." \n... ) as client : \n... # Set 5 GB limit \n... limit = 5 * 1024 ** 3 # 5 GB in bytes \n... await client . set_access_key_data_limit ( 1 , limit ) \n... \n... # Verify limit \n... key = await client . get_access_key ( 1 ) \n... assert key . data_limit and key . data_limit . bytes == limit \n
\n
\n \n", "signature": "(self , key_id : int , bytes_limit : int ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.AsyncOutlineClient.remove_access_key_data_limit", "modulename": "pyoutlineapi", "qualname": "AsyncOutlineClient.remove_access_key_data_limit", "kind": "function", "doc": "Remove data transfer limit from access key.
\n\nArguments: \n\n\nkey_id: Access key ID \n \n\nReturns: \n\n\n True if successful
\n \n\nRaises: \n\n\nAPIError: If key doesn't exist \n \n", "signature": "(self , key_id : str ) -> bool : ", "funcdef": "async def"}, {"fullname": "pyoutlineapi.OutlineError", "modulename": "pyoutlineapi", "qualname": "OutlineError", "kind": "class", "doc": "Base exception for Outline client errors.
\n", "bases": "builtins.Exception"}, {"fullname": "pyoutlineapi.APIError", "modulename": "pyoutlineapi", "qualname": "APIError", "kind": "class", "doc": "Raised when API requests fail.
\n", "bases": "pyoutlineapi.client.OutlineError"}, {"fullname": "pyoutlineapi.AccessKey", "modulename": "pyoutlineapi", "qualname": "AccessKey", "kind": "class", "doc": "Access key details.
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.AccessKey.id", "modulename": "pyoutlineapi", "qualname": "AccessKey.id", "kind": "variable", "doc": "
\n", "annotation": ": int"}, {"fullname": "pyoutlineapi.AccessKey.name", "modulename": "pyoutlineapi", "qualname": "AccessKey.name", "kind": "variable", "doc": "
\n", "annotation": ": Optional[str]"}, {"fullname": "pyoutlineapi.AccessKey.password", "modulename": "pyoutlineapi", "qualname": "AccessKey.password", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.AccessKey.port", "modulename": "pyoutlineapi", "qualname": "AccessKey.port", "kind": "variable", "doc": "
\n", "annotation": ": int"}, {"fullname": "pyoutlineapi.AccessKey.method", "modulename": "pyoutlineapi", "qualname": "AccessKey.method", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.AccessKey.access_url", "modulename": "pyoutlineapi", "qualname": "AccessKey.access_url", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.AccessKey.data_limit", "modulename": "pyoutlineapi", "qualname": "AccessKey.data_limit", "kind": "variable", "doc": "
\n", "annotation": ": Optional[pyoutlineapi.models.DataLimit]"}, {"fullname": "pyoutlineapi.AccessKey.model_config", "modulename": "pyoutlineapi", "qualname": "AccessKey.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest", "kind": "class", "doc": "Request parameters for creating an access key.\nPer OpenAPI: /access-keys POST request body
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.name", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.name", "kind": "variable", "doc": "
\n", "annotation": ": Optional[str]"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.method", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.method", "kind": "variable", "doc": "
\n", "annotation": ": Optional[str]"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.password", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.password", "kind": "variable", "doc": "
\n", "annotation": ": Optional[str]"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.port", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.port", "kind": "variable", "doc": "
\n", "annotation": ": Optional[int]"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.limit", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.limit", "kind": "variable", "doc": "
\n", "annotation": ": Optional[pyoutlineapi.models.DataLimit]"}, {"fullname": "pyoutlineapi.AccessKeyCreateRequest.model_config", "modulename": "pyoutlineapi", "qualname": "AccessKeyCreateRequest.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.AccessKeyList", "modulename": "pyoutlineapi", "qualname": "AccessKeyList", "kind": "class", "doc": "List of access keys.
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.AccessKeyList.access_keys", "modulename": "pyoutlineapi", "qualname": "AccessKeyList.access_keys", "kind": "variable", "doc": "
\n", "annotation": ": list[pyoutlineapi.models.AccessKey]"}, {"fullname": "pyoutlineapi.AccessKeyList.model_config", "modulename": "pyoutlineapi", "qualname": "AccessKeyList.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.DataLimit", "modulename": "pyoutlineapi", "qualname": "DataLimit", "kind": "class", "doc": "Data transfer limit configuration.
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.DataLimit.bytes", "modulename": "pyoutlineapi", "qualname": "DataLimit.bytes", "kind": "variable", "doc": "
\n", "annotation": ": int"}, {"fullname": "pyoutlineapi.DataLimit.validate_bytes", "modulename": "pyoutlineapi", "qualname": "DataLimit.validate_bytes", "kind": "function", "doc": "
\n", "signature": "(cls , v : int ) -> int : ", "funcdef": "def"}, {"fullname": "pyoutlineapi.DataLimit.model_config", "modulename": "pyoutlineapi", "qualname": "DataLimit.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.ErrorResponse", "modulename": "pyoutlineapi", "qualname": "ErrorResponse", "kind": "class", "doc": "Error response structure\nPer OpenAPI: 404 and 400 responses
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.ErrorResponse.code", "modulename": "pyoutlineapi", "qualname": "ErrorResponse.code", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.ErrorResponse.message", "modulename": "pyoutlineapi", "qualname": "ErrorResponse.message", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.ErrorResponse.model_config", "modulename": "pyoutlineapi", "qualname": "ErrorResponse.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.ExperimentalMetrics", "modulename": "pyoutlineapi", "qualname": "ExperimentalMetrics", "kind": "class", "doc": "Experimental metrics data structure\nPer OpenAPI: /experimental/server/metrics endpoint
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.ExperimentalMetrics.server", "modulename": "pyoutlineapi", "qualname": "ExperimentalMetrics.server", "kind": "variable", "doc": "
\n", "annotation": ": list[pyoutlineapi.models.ServerMetric]"}, {"fullname": "pyoutlineapi.ExperimentalMetrics.access_keys", "modulename": "pyoutlineapi", "qualname": "ExperimentalMetrics.access_keys", "kind": "variable", "doc": "
\n", "annotation": ": list[pyoutlineapi.models.AccessKeyMetric]"}, {"fullname": "pyoutlineapi.ExperimentalMetrics.model_config", "modulename": "pyoutlineapi", "qualname": "ExperimentalMetrics.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.MetricsPeriod", "modulename": "pyoutlineapi", "qualname": "MetricsPeriod", "kind": "class", "doc": "Time periods for metrics collection.
\n", "bases": "builtins.str, enum.Enum"}, {"fullname": "pyoutlineapi.MetricsPeriod.DAILY", "modulename": "pyoutlineapi", "qualname": "MetricsPeriod.DAILY", "kind": "variable", "doc": "
\n", "default_value": "<MetricsPeriod.DAILY: 'daily'>"}, {"fullname": "pyoutlineapi.MetricsPeriod.WEEKLY", "modulename": "pyoutlineapi", "qualname": "MetricsPeriod.WEEKLY", "kind": "variable", "doc": "
\n", "default_value": "<MetricsPeriod.WEEKLY: 'weekly'>"}, {"fullname": "pyoutlineapi.MetricsPeriod.MONTHLY", "modulename": "pyoutlineapi", "qualname": "MetricsPeriod.MONTHLY", "kind": "variable", "doc": "
\n", "default_value": "<MetricsPeriod.MONTHLY: 'monthly'>"}, {"fullname": "pyoutlineapi.MetricsStatusResponse", "modulename": "pyoutlineapi", "qualname": "MetricsStatusResponse", "kind": "class", "doc": "Response for /metrics/enabled endpoint
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.MetricsStatusResponse.metrics_enabled", "modulename": "pyoutlineapi", "qualname": "MetricsStatusResponse.metrics_enabled", "kind": "variable", "doc": "
\n", "annotation": ": bool"}, {"fullname": "pyoutlineapi.MetricsStatusResponse.model_config", "modulename": "pyoutlineapi", "qualname": "MetricsStatusResponse.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.Server", "modulename": "pyoutlineapi", "qualname": "Server", "kind": "class", "doc": "Server information.\nPer OpenAPI: /server endpoint schema
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.Server.name", "modulename": "pyoutlineapi", "qualname": "Server.name", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.Server.server_id", "modulename": "pyoutlineapi", "qualname": "Server.server_id", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.Server.metrics_enabled", "modulename": "pyoutlineapi", "qualname": "Server.metrics_enabled", "kind": "variable", "doc": "
\n", "annotation": ": bool"}, {"fullname": "pyoutlineapi.Server.created_timestamp_ms", "modulename": "pyoutlineapi", "qualname": "Server.created_timestamp_ms", "kind": "variable", "doc": "
\n", "annotation": ": int"}, {"fullname": "pyoutlineapi.Server.version", "modulename": "pyoutlineapi", "qualname": "Server.version", "kind": "variable", "doc": "
\n", "annotation": ": str"}, {"fullname": "pyoutlineapi.Server.port_for_new_access_keys", "modulename": "pyoutlineapi", "qualname": "Server.port_for_new_access_keys", "kind": "variable", "doc": "
\n", "annotation": ": int"}, {"fullname": "pyoutlineapi.Server.hostname_for_access_keys", "modulename": "pyoutlineapi", "qualname": "Server.hostname_for_access_keys", "kind": "variable", "doc": "
\n", "annotation": ": Optional[str]"}, {"fullname": "pyoutlineapi.Server.access_key_data_limit", "modulename": "pyoutlineapi", "qualname": "Server.access_key_data_limit", "kind": "variable", "doc": "
\n", "annotation": ": Optional[pyoutlineapi.models.DataLimit]"}, {"fullname": "pyoutlineapi.Server.model_config", "modulename": "pyoutlineapi", "qualname": "Server.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}, {"fullname": "pyoutlineapi.ServerMetrics", "modulename": "pyoutlineapi", "qualname": "ServerMetrics", "kind": "class", "doc": "Server metrics data for data transferred per access key\nPer OpenAPI: /metrics/transfer endpoint
\n", "bases": "pydantic.main.BaseModel"}, {"fullname": "pyoutlineapi.ServerMetrics.bytes_transferred_by_user_id", "modulename": "pyoutlineapi", "qualname": "ServerMetrics.bytes_transferred_by_user_id", "kind": "variable", "doc": "
\n", "annotation": ": dict[str, int]"}, {"fullname": "pyoutlineapi.ServerMetrics.model_config", "modulename": "pyoutlineapi", "qualname": "ServerMetrics.model_config", "kind": "variable", "doc": "Configuration for the model, should be a dictionary conforming to [ConfigDict
][pydantic.config.ConfigDict].
\n", "annotation": ": ClassVar[pydantic.config.ConfigDict]", "default_value": "{}"}];
+
+ // mirrored in build-search-index.js (part 1)
+ // Also split on html tags. this is a cheap heuristic, but good enough.
+ elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/);
+
+ let searchIndex;
+ if (docs._isPrebuiltIndex) {
+ console.info("using precompiled search index");
+ searchIndex = elasticlunr.Index.load(docs);
+ } else {
+ console.time("building search index");
+ // mirrored in build-search-index.js (part 2)
+ searchIndex = elasticlunr(function () {
+ this.pipeline.remove(elasticlunr.stemmer);
+ this.pipeline.remove(elasticlunr.stopWordFilter);
+ this.addField("qualname");
+ this.addField("fullname");
+ this.addField("annotation");
+ this.addField("default_value");
+ this.addField("signature");
+ this.addField("bases");
+ this.addField("doc");
+ this.setRef("fullname");
+ });
+ for (let doc of docs) {
+ searchIndex.addDoc(doc);
+ }
+ console.timeEnd("building search index");
+ }
+
+ return (term) => searchIndex.search(term, {
+ fields: {
+ qualname: {boost: 4},
+ fullname: {boost: 2},
+ annotation: {boost: 2},
+ default_value: {boost: 2},
+ signature: {boost: 2},
+ bases: {boost: 2},
+ doc: {boost: 1},
+ },
+ expand: true
+ });
+})();
\ No newline at end of file