Coverage for src/paperap/settings.py: 96%
53 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-20 13:17 -0400
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-20 13:17 -0400
1"""
2----------------------------------------------------------------------------
4METADATA:
6File: settings.py
7 Project: paperap
8Created: 2025-03-09
9 Version: 0.0.8
10Author: Jess Mann
11Email: jess@jmann.me
12 Copyright (c) 2025 Jess Mann
14----------------------------------------------------------------------------
16LAST MODIFIED:
182025-03-09 By Jess Mann
20"""
22from __future__ import annotations
24from typing import Annotated, Any, Optional, Self, TypedDict, override
26from pydantic import Field, HttpUrl, field_validator
27from pydantic_settings import BaseSettings, SettingsConfigDict
29from paperap.exceptions import ConfigurationError
32class SettingsArgs(TypedDict, total=False):
33 """
34 Arguments for the settings class
35 """
37 base_url: HttpUrl
38 token: str | None
39 username: str | None
40 password: str | None
41 timeout: int
42 require_ssl: bool
43 save_on_write: bool
46class Settings(BaseSettings):
47 """
48 Settings for the paperap library
49 """
51 token: str | None = None
52 username: str | None = None
53 password: str | None = None
54 base_url: HttpUrl
55 timeout: int = 60
56 require_ssl: bool = False
57 save_on_write: bool = True
58 openai_key: str | None = Field(default=None, alias="openai_api_key")
59 openai_model: str | None = Field(default=None, alias="openai_model_name")
60 openai_url: str | None = Field(default=None, alias="openai_base_url")
62 model_config = SettingsConfigDict(env_prefix="PAPERLESS_", extra="ignore")
64 @field_validator("base_url", mode="after")
65 @classmethod
66 def validate_url(cls, value: HttpUrl) -> HttpUrl:
67 """Ensure the URL is properly formatted."""
68 # Make sure the URL has a scheme
69 if not all([value.scheme, value.host]):
70 raise ConfigurationError("Base URL must have a scheme and host")
72 return value
74 @field_validator("timeout", mode="before")
75 @classmethod
76 def validate_timeout(cls, value: Any) -> int:
77 """Ensure the timeout is a positive integer."""
78 try:
79 if isinstance(value, str):
80 # May raise ValueError
81 value = int(value)
83 if not isinstance(value, int):
84 raise TypeError("Unknown type for timeout")
85 except ValueError as ve:
86 raise TypeError(f"Timeout must be an integer. Provided {value=} of type {type(value)}") from ve
88 if value < 0:
89 raise ConfigurationError("Timeout must be a positive integer")
90 return value
92 @override
93 def model_post_init(self, __context: Any):
94 """
95 Validate the settings after they have been initialized.
96 """
97 if self.token is None and (self.username is None or self.password is None):
98 raise ConfigurationError("Provide a token, or a username and password")
100 if not self.base_url:
101 raise ConfigurationError("Base URL is required")
103 if self.require_ssl and self.base_url.scheme != "https":
104 raise ConfigurationError(f"URL must use HTTPS. Url: {self.base_url}. Scheme: {self.base_url.scheme}")
106 return super().model_post_init(__context)