Skip to content

Commit 4ee0878

Browse files
authored
feat: adds object classes (#104)
* feat: adds LemmyUser object class * feat: adds lemmy.get_user() which returns a LemmyUser class * Requires python 3.10+ from now
1 parent a1a5893 commit 4ee0878

File tree

7 files changed

+194
-3
lines changed

7 files changed

+194
-3
lines changed

examples/user_class.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
## python examples/user.py db0
2+
3+
import argparse
4+
import json
5+
import os
6+
from pythorhead import Lemmy
7+
8+
arg_parser = argparse.ArgumentParser()
9+
arg_parser.add_argument("username", action="store")
10+
arg_parser.add_argument(
11+
"-d",
12+
"--lemmy_domain",
13+
action="store",
14+
required=False,
15+
type=str,
16+
help="the domain in which to look for this user",
17+
)
18+
arg_parser.add_argument(
19+
"-u",
20+
"--lemmy_username",
21+
action="store",
22+
required=False,
23+
type=str,
24+
help="Which user to authenticate as",
25+
)
26+
arg_parser.add_argument(
27+
"-p",
28+
"--lemmy_password",
29+
action="store",
30+
required=False,
31+
type=str,
32+
help="Which password to authenticate with",
33+
)
34+
args = arg_parser.parse_args()
35+
36+
37+
lemmy_domain = args.lemmy_domain
38+
if not lemmy_domain:
39+
lemmy_domain = os.getenv("LEMMY_DOMAIN", "lemmy.dbzer0.com")
40+
if not lemmy_domain:
41+
raise Exception("You need to provide a lemmy domain via env var or arg")
42+
43+
lemmy_username = args.lemmy_username
44+
if not lemmy_username:
45+
lemmy_username = os.getenv("LEMMY_USERNAME")
46+
47+
lemmy_password = args.lemmy_password
48+
if not lemmy_password:
49+
lemmy_password = os.getenv("LEMMY_PASSWORD")
50+
51+
lemmy = Lemmy(f"https://{lemmy_domain}", raise_exceptions=True, request_timeout=2)
52+
if lemmy_username and lemmy_password:
53+
login = lemmy.log_in(lemmy_username, lemmy_password)
54+
user = lemmy.user.get(username=args.username, return_user_object = True)
55+
if user:
56+
print(user.asjson(indent=4))
57+
else:
58+
print("no matching username found")

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ authors = [
1010
]
1111
version = "v0.26.0"
1212
readme = "README.md"
13-
requires-python = ">=3.8"
13+
requires-python = ">=3.10"
1414
license = { file="LICENSE" }
1515
classifiers = [
1616
"Programming Language :: Python :: 3",

pythorhead/classes/__init__.py

Whitespace-only changes.

pythorhead/classes/user.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from dataclasses import dataclass, asdict
2+
from datetime import datetime
3+
from pythorhead import lemmy
4+
from pythorhead.types import SortType, ListingType, LanguageType
5+
from dateutil import parser
6+
from pythorhead.utils import json_serializer
7+
8+
import json
9+
10+
@dataclass
11+
class LemmyUser:
12+
id: int
13+
name: str
14+
banned: bool
15+
published: datetime
16+
actor_id: str
17+
local: bool
18+
deleted: bool
19+
bot_account: bool
20+
instance_id: int
21+
is_admin: bool
22+
display_name: str | None = None
23+
bio: str | None = None
24+
avatar: str | None = None
25+
banner: str | None = None
26+
matrix_user_id: str | None = None
27+
ban_expires: datetime | None = None
28+
updated: datetime | None = None
29+
# The owning lemmy instance. We use it to reach the API classes
30+
lemmy = None
31+
32+
@classmethod
33+
def from_dict(cls, person_dict: dict, lemmy) -> 'LemmyUser':
34+
# Convert string to datetime for ban_expires if it exists
35+
for key in {'ban_expires', 'updates', 'published'}:
36+
if key in person_dict and person_dict[key]:
37+
person_dict[key] = parser.isoparse(person_dict[key])
38+
new_user = cls(**person_dict)
39+
new_user.lemmy = lemmy
40+
return new_user
41+
42+
def refresh(self) -> None:
43+
"""
44+
Update instance attributes from a new dictionary while preserving custom fields.
45+
46+
Args:
47+
new_data: Dictionary containing updated user data
48+
"""
49+
new_data = self.lemmy.user.get(person_id=self.person_id)
50+
# Handle datetime conversion
51+
if 'ban_expires' in new_data and new_data['ban_expires']:
52+
new_data['ban_expires'] = datetime.fromisoformat(new_data['ban_expires'])
53+
54+
for field_name in new_data:
55+
setattr(self, field_name, new_data[field_name])
56+
57+
58+
def purge(self) -> None:
59+
self.lemmy.user.purge(person_id=self.person_id)
60+
61+
def ban(self,
62+
ban: bool = True,
63+
expires: datetime | int | None = None,
64+
reason: str | None = None,
65+
remove_data: bool | None = None
66+
) -> None:
67+
self.lemmy.user.ban(
68+
person_id=self.person_id,
69+
ban = ban,
70+
expires = expires,
71+
reason = reason,
72+
remove_data = remove_data,
73+
)
74+
self.refresh()
75+
76+
def update(self):
77+
if self.lemmy.user._requestor.logged_in_username != self.name:
78+
raise Exception("Cannot update user details for anyone but the currently logged-in user.")
79+
self.lemmy.user.save_user_settings(
80+
avatar=self.avatar,
81+
banner=self.banner,
82+
display_name=self.display_name,
83+
bio=self.bio,
84+
matrix_user_id=self.matrix_user_id,
85+
bot_account=self.bot_account,
86+
)
87+
self.refresh()
88+
89+
def set_settings(self, **kwargs):
90+
if self.lemmy.user._requestor.logged_in_username != self.name:
91+
raise Exception("Cannot update user settings for anyone but the currently logged-in user.")
92+
self.lemmy.user.save_user_settings(**kwargs)
93+
self.refresh()
94+
95+
def asdict(self):
96+
return asdict(self)
97+
98+
def asjson(self, indent=4):
99+
selfdict = self.asdict()
100+
return json.dumps(selfdict, indent=indent, default=json_serializer)
101+
102+
def pm(self, content):
103+
self.lemmy.private_message(
104+
content=content,
105+
recipient_id=self.id,
106+
)

pythorhead/lemmy.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import annotations
12
import logging
23
import time
34
from typing import Any, Optional
@@ -13,12 +14,22 @@
1314
from pythorhead.types import FeatureType, ListingType, SortType, SearchType, SearchOption
1415
from pythorhead.user import User
1516
from pythorhead.admin import Admin
17+
from pythorhead.classes.user import LemmyUser
1618

1719
logger = logging.getLogger(__name__)
1820

1921
class Lemmy:
2022
_known_communities = {}
2123
_requestor: Requestor
24+
post: Post
25+
community: Community
26+
comment: Comment
27+
site: Site
28+
user: User
29+
private_message: PrivateMessage
30+
image: Image
31+
mention: Mention
32+
admin: Admin
2233

2334
def __init__(self, api_base_url: str, raise_exceptions = False, request_timeout=3) -> None:
2435
self._requestor = Requestor(raise_exceptions, request_timeout)
@@ -79,6 +90,14 @@ def discover_community(self, community_name: str, search=SearchOption.Retry) ->
7990
self._known_communities[community_name] = community_id
8091
return community_id
8192

93+
def get_user(self, **kwargs) -> LemmyUser | None:
94+
user_json = self.user.get(**kwargs)
95+
if user_json is None:
96+
return
97+
user_dict = user_json['person_view']['person']
98+
user_dict['is_admin'] = user_json['person_view']['is_admin']
99+
return LemmyUser.from_dict(user_dict, self)
100+
82101
def search(
83102
self,
84103
q: str,

pythorhead/user.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from pythorhead.requestor import Request, Requestor
44
from pythorhead.types import SortType, ListingType, LanguageType
55

6-
76
class User:
87
def __init__(self, _requestor: Requestor):
98
self._requestor = _requestor
@@ -17,7 +16,8 @@ def get(
1716
limit: Optional[int] = None,
1817
community_id: Optional[int] = None,
1918
saved_only: Optional[bool] = None,
20-
) -> Optional[dict]:
19+
return_user_object = False,
20+
) -> dict | None:
2121
"""
2222
Get user details with various filters.
2323
@@ -29,6 +29,7 @@ def get(
2929
limit (Optional[int], optional): Defaults to None.
3030
community_id (Optional[int], optional): Defaults to None.
3131
saved_only (Optional[bool], optional): Defaults to None.
32+
return_user_object: (Optional[bool], optional): If true, returns a LemmyUser object, instead of the raw dict
3233
Returns:
3334
dict: user view
3435
"""

pythorhead/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from datetime import datetime
2+
3+
def json_serializer(obj):
4+
if isinstance(obj, datetime):
5+
return obj.isoformat() # Convert datetime to ISO 8601 string
6+
else:
7+
return obj

0 commit comments

Comments
 (0)