The Basics#
Type safety#
The library is meticulous about static typing and it expects your code to be as well. It is strongly advised that type annotations are used all throughout your code to make it fully typed (achieved mostly by adding type annotations to all function signatures), and then use a type checker to verify type correctness in your program.
IDEs such as VS Code and PyCharm have built-in type checkers. If you’re using a text editor, popular static type checker tools include Mypy and Pyright.
Both are recommended. Mypy’s output looks nicer, but Pyright is the strongest Python type checker.
The default settings for type checkers tend to be too lenient, while their
strict option can be overly strict. The mypy.ini
(Mypy) and
pyrightconfig.json
(Pyright) files in the RedditWarp repository provide a
well-balanced middle ground, and it is recommended to use them as a basis for
your own projects if you don’t already have a template.
Two IO worlds#
You would have noticed by now the unusual capitalisation of the terms SYNC
and ASYNC
in many of RedditWarp’s imports. This is how the library chooses
to support both sync and async IO worlds.
Switching a program’s synchronicity is straightforward. To convert a program
into an asynchronous one, all you need to do is change SYNC
to ASYNC
in the
imports, add async
and await
keywords to the appropriate locations, and
then wrap main()
in asyncio.run()
. A type checker will greatly assist you
in this process.
#!/usr/bin/env python
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from redditwarp.models.message_SYNC import MailboxMessage
import redditwarp.SYNC
from redditwarp.models.message_SYNC import ComposedMessage
from redditwarp.streaming.makers.message_SYNC import create_inbox_message_stream
from redditwarp.streaming.SYNC import flow
def main() -> None:
client = redditwarp.SYNC.Client()
with client:
inbox_message_stream = create_inbox_message_stream(client)
@inbox_message_stream.output.attach
def _(mesg: MailboxMessage) -> None:
if isinstance(mesg, ComposedMessage):
if mesg.subject.startswith('!ping'):
client.p.message.send(
mesg.author_name,
're: ' + mesg.subject,
'pong',
)
flow(inbox_message_stream)
main()
#!/usr/bin/env python
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from redditwarp.models.message_ASYNC import MailboxMessage
import asyncio
import redditwarp.ASYNC
from redditwarp.models.message_ASYNC import ComposedMessage
from redditwarp.streaming.makers.message_ASYNC import create_inbox_message_stream
from redditwarp.streaming.ASYNC import flow
async def main() -> None:
client = redditwarp.ASYNC.Client()
async with client:
inbox_message_stream = create_inbox_message_stream(client)
@inbox_message_stream.output.attach
async def _(mesg: MailboxMessage) -> None:
if isinstance(mesg, ComposedMessage):
if mesg.subject.startswith('!ping'):
await client.p.message.send(
mesg.author_name,
're: ' + mesg.subject,
'pong',
)
await flow(inbox_message_stream)
asyncio.run(main())
To help internalise the convention that’s going on here, the SYNC.py
and
ASYNC.py
modules can be thought of as everything that would be in the
__init__.py
file but isn’t because it is committed to a particular IO world.
When a module ends with a capital SYNC
/ASYNC
it signifies that an analogous
symbol exists in the the opposite module which can be mindlessly switched out
for if you decide to switch the synchronicity of your program.
Sometimes, not all IO-committed objects have a perfect counterpart and the name
of the symbol may differ slightly for the sake of correctness, even though they
offer the same functionality. In these cases, the symbols will not be found in
module names ending with SYNC
/ASYNC
, but rather in module names ending with
sync1
/async1
. It’s rare you’ll encounter this though.
# SYNC
from redditwarp.pagination.paginators.subreddit_sync1 import SubredditSearchPaginator
# ASYNC
from redditwarp.pagination.paginators.subreddit_async1 import SubredditSearchAsyncPaginator
If it makes sense to use the sync version in an async program, the symbols will be named without any specific convention. This typically occurs with iterators.
# SYNC
from redditwarp.iterators.call_chunk_chaining_iterator import CallChunkChainingIterator
# ASYNC
from redditwarp.iterators.call_chunk_chaining_async_iterator import CallChunkChainingAsyncIterator
Model attributes#
Procedure index methods return instances of model objects from the
classes defined in the redditwarp.models
subpackage.
The attributes of the models have been deliberately named to be more meaningful and consistent, and only the most essential ones have been wired up.
The models are never mutated by the library once they are created, and modifying the attributes on these objects is not recommended.
A model’s original data dictionary can be found in a .d
attribute on the
model.
>>> subm = client.p.submission.fetch('10gudzi')
>>> subm.permalink
'https://www.reddit.com/r/confusing_perspective/comments/10gudzi/skydiving_fun/'
>>> subm.d['permalink']
'/r/confusing_perspective/comments/10gudzi/skydiving_fun/'
>>> assert subm.permalink_path == subm.d['permalink']
>>> assert 'permalink_path' not in subm.d
>>> subm.created_at
datetime.datetime(2023, 1, 20, 11, 16, 19, tzinfo=datetime.timezone.utc)
>>> assert 'created_at' not in subm.d
>>> subm.created_ut
1674213379
>>> assert subm.created_ut == subm.d['created_utc']
Oftentimes if there is a .d
attribute then there will also be a .b
attribute which allows access to the items in the .d
attribute using dot
notation. E.g.:
assert subm.d['created_utc'] == subm.b.created_utc == subm.b['created_utc']
It is best to avoid using the .d
and .b
attribute namespaces as they do not
provide the same level of type-safety as directly accessing attributes on the
object itself does.
Models can also have methods, but they are merely passthroughs to the procedure index methods.
subm.delete()
# <== Functionally identical ==>
client.p.submission.delete(subm.idn)
Models coming from a SYNC
/ASYNC
module will often have a non-IO-committed
version with no methods. If you don’t intend to use the model methods you can
type your variables as the non-IO version.
E.g.:
import redditwarp.SYNC
from redditwarp.models.submission_SYNC import Submission as Submission_IO
from redditwarp.models.submission import Submission
client = redditwarp.SYNC.Client()
subm1: Submission_IO = client.p.submission.fetch(2196778693)
subm1.delete() # Valid
subm2: Submission = subm1
subm2.delete() # Invalid => Mypy :: "Submission" has no attribute "delete"