1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
|
import json
from dataclasses import dataclass, field
from typing import ClassVar, Dict, List, Optional
import requests
from internetarchive import get_session
from internetarchive.exceptions import AccountAPIError
from internetarchive.session import ArchiveSession
"""
internetarchive.account
~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (C) 2012-2025 by Internet Archive.
:license: AGPL 3, see LICENSE for more details.
This module provides the `Account` class for interacting with user accounts on the
Internet Archive. It requires administrative privileges.
"""
@dataclass
class Account:
"""
A class for interacting with user accounts on the Internet Archive.
Note:
This class requires administrative privileges.
This class provides methods to:
- Fetch account details using various identifiers (e.g., email, screenname, itemname).
- Lock and unlock accounts.
- Convert account data to a dictionary for serialization.
Example Usage:
>>> from internetarchive.account import Account
>>> account = Account.from_account_lookup('email', 'foo@example.com')
>>> account.lock(comment="Locked spam account")
>>> print(account.to_dict())
"""
locked: bool
verified: bool
email: str
canonical_email: str
itemname: str
screenname: str
notifications: List[str]
has_disability_access: bool
lastlogin: str
createdate: str
session: ArchiveSession = field(default_factory=get_session)
API_BASE_URL: str = '/services/xauthn/'
API_INFO_PARAMS: ClassVar[Dict[str, str]] = {'op': 'info'}
API_LOCK_UNLOCK_PARAMS: ClassVar[Dict[str, str]] = {'op': 'lock_unlock'}
def _get_api_base_url(self) -> str:
"""Dynamically construct the API base URL using the session's host."""
return f'https://{self.session.host}{self.API_BASE_URL}' # type: ignore[attr-defined]
def _post_api_request(
self,
endpoint: str,
params: Dict[str, str],
data: Dict[str, str],
session: Optional[ArchiveSession] = None
) -> requests.Response:
"""
Helper method to make API requests.
Args:
endpoint: The API endpoint to call.
params: Query parameters for the request.
data: Data to send in the request body.
session: Optional session to use for the request. Defaults to self.session.
Returns:
The response from the API.
Raises:
requests.exceptions.RequestException: If the API request fails.
"""
session = session or self.session
url = f'https://{session.host}{endpoint}' # type: ignore[attr-defined]
response = session.post(url, params=params, data=data)
response.raise_for_status()
return response
@classmethod
def from_account_lookup(
cls,
identifier_type: str,
identifier: str,
session: Optional[ArchiveSession] = None
) -> "Account":
"""
Factory method to initialize an Account using an identifier type and value.
Args:
identifier_type: The type of identifier (e.g., 'email', 'screenname').
identifier: The value of the identifier (e.g., 'foo@example.com').
session: Optional session to use for the request.
Returns:
An instance of Account.
"""
json_data = cls._fetch_account_data_from_api(identifier_type, identifier, session)
return cls.from_json(json_data, session)
@classmethod
def _fetch_account_data_from_api(
cls,
identifier_type: str,
identifier: str,
session: Optional[ArchiveSession] = None
) -> Dict:
"""
Fetches account data from the API using an identifier type and value.
Args:
identifier_type: The type of identifier (e.g., 'email', 'screenname').
identifier: The value of the identifier (e.g., 'foo@example.com').
session: Optional session to use for the request.
Returns:
A dictionary containing the account data.
Raises:
requests.exceptions.RequestException: If the API request fails.
ValueError: If the API response is invalid or missing required data.
"""
data = {identifier_type: identifier}
session = session or get_session()
try:
response = session.post(
f'https://{session.host}{cls.API_BASE_URL}', # type: ignore[attr-defined]
params=cls.API_INFO_PARAMS,
data=json.dumps(data)
)
response.raise_for_status()
j = response.json()
if j.get("error") or not j.get("values"):
raise AccountAPIError(j.get("error", "Unknown error"), error_data=j)
return j["values"]
except requests.exceptions.RequestException as e:
raise AccountAPIError(f"Failed to fetch account data: {e}")
@classmethod
def from_json(
cls,
json_data: Dict,
session: Optional[ArchiveSession] = None
) -> "Account":
"""
Factory method to initialize an Account using JSON data.
Args:
json_data: A dictionary containing account data.
session: Optional session to use for the request.
Returns:
An instance of Account.
Raises:
ValueError: If required fields are missing in the JSON data.
"""
required_fields = [
"canonical_email",
"email",
"has_disability_access",
"itemname",
"locked",
"notifications",
"screenname",
"verified",
"lastlogin",
"createdate",
]
for requried_field in required_fields:
if requried_field not in json_data:
raise ValueError(f"Missing required requried_field in JSON data: {requried_field}")
# Ensure session is of type ArchiveSession
if session is None:
session = get_session() # Default to ArchiveSession
elif not isinstance(session, ArchiveSession):
raise TypeError(f"Expected session to be of type ArchiveSession, got {type(session)}")
return cls(
locked=json_data["locked"],
verified=json_data["verified"],
email=json_data["email"],
canonical_email=json_data["canonical_email"],
itemname=json_data["itemname"],
screenname=json_data["screenname"],
notifications=json_data["notifications"],
has_disability_access=json_data["has_disability_access"],
lastlogin=json_data["lastlogin"],
createdate=json_data["createdate"],
session=session
)
def lock(self,
comment: Optional[str] = None,
session: Optional[ArchiveSession] = None) -> requests.Response:
"""
Lock the account.
Args:
comment: An optional comment for the lock operation.
session: Optional session to use for the request.
Returns:
The response from the API.
"""
data = {'itemname': self.itemname, 'is_lock': '1'}
if comment:
data['comments'] = comment
return self._post_api_request(
self.API_BASE_URL,
params=self.API_LOCK_UNLOCK_PARAMS,
data=data,
session=session
)
def unlock(self,
comment: Optional[str] = None,
session: Optional[ArchiveSession] = None) -> requests.Response:
"""
Unlock the account.
Args:
comment: An optional comment for the unlock operation.
session: Optional session to use for the request.
Returns:
The response from the API.
"""
data = {'itemname': self.itemname, 'is_lock': '0'}
if comment:
data['comments'] = comment
return self._post_api_request(
self.API_BASE_URL,
params=self.API_LOCK_UNLOCK_PARAMS,
data=data,
session=session
)
def to_dict(self) -> Dict:
"""
Converts the Account instance to a dictionary.
Returns:
A dictionary representation of the Account instance.
"""
return {
"locked": self.locked,
"verified": self.verified,
"email": self.email,
"canonical_email": self.canonical_email,
"itemname": self.itemname,
"screenname": self.screenname,
"notifications": self.notifications,
"has_disability_access": self.has_disability_access,
"lastlogin": self.lastlogin,
"createdate": self.createdate,
}
|