redditadmin 0.0.6__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
redditadmin/core.py CHANGED
@@ -1,43 +1,36 @@
1
1
  import logging
2
2
  import os
3
3
  import signal
4
- import sys
5
4
  import time
6
- from abc import ABC, abstractmethod
5
+ from abc import ABCMeta, abstractmethod
7
6
  from logging.handlers import TimedRotatingFileHandler
8
7
  from pathlib import Path
9
8
  from typing import List
10
9
 
11
- from .utility.botcredentials import BotCredentials
12
- from .utility.exceptions import BotInitializationError, InvalidBotCredentialsError
10
+ from .utility.botcredentials import InvalidBotCredentialsError, BotCredentials,\
11
+ BotCredentialsImplementation
12
+ from .utility.miscellaneous import BotInitializationError
13
13
  from .plugin.asynchronouspluginsexecutor import AsynchronousPluginsExecutor
14
- from .plugin.exceptions import PluginsExecutorInitializationError
15
14
  from .plugin.plugin import Plugin
16
- from .plugin.pluginsexecutor import PluginsExecutor
17
- from .plugin.redditinterfacefactory import RedditInterfaceFactory
15
+ from .plugin.pluginsexecutor import PluginsExecutor, PluginsExecutorInitializationError
16
+ from .plugin.redditinterfacefactory import RedditInterfaceFactory, DefaultRedditInterfaceFactory
18
17
 
19
18
 
20
- class RedditAdmin(ABC):
21
- """Type encapsulating a RedditAdmin instance"""
22
-
23
- def __init__(self, *args):
24
- pass
19
+ class RedditAdmin(metaclass=ABCMeta):
20
+ """Encapsulates RedditAdmin bot"""
25
21
 
26
22
  @abstractmethod
27
23
  def run(self, bot_credentials: BotCredentials, listen: bool = False):
28
24
  """Run the bot"""
29
-
30
- raise NotImplementedError
25
+ ...
31
26
 
32
27
  @abstractmethod
33
- def stop(self):
28
+ def stop(self, wait: bool):
34
29
  """Shutdown the bot"""
30
+ ...
35
31
 
36
- raise NotImplementedError
37
32
 
38
-
39
- class RedditAdminImplementation(RedditAdmin):
40
- """Reddit Admin Bot"""
33
+ class _RedditAdminImplementation(RedditAdmin):
41
34
 
42
35
  __plugins: List[Plugin]
43
36
  __pluginsExecutor: PluginsExecutor
@@ -106,7 +99,7 @@ class RedditAdminImplementation(RedditAdmin):
106
99
  # Setting the default console logging level global variable
107
100
  self.__defaultConsoleLoggingLevel = console_handler.level
108
101
 
109
- def ___get_new_bot_credentials(self) -> BotCredentials:
102
+ def __get_new_bot_credentials(self) -> BotCredentials:
110
103
  """Convenience method to retrieve bot credentials from user input"""
111
104
 
112
105
  try:
@@ -125,7 +118,7 @@ class RedditAdminImplementation(RedditAdmin):
125
118
  # Resume console logging
126
119
  self.__resume_console_logging()
127
120
 
128
- return BotCredentials(
121
+ return BotCredentialsImplementation(
129
122
  user_agent, client_id,
130
123
  client_secret, username,
131
124
  password
@@ -144,7 +137,7 @@ class RedditAdminImplementation(RedditAdmin):
144
137
  # instance from provided credentials
145
138
 
146
139
  try:
147
- reddit_interface_factory = RedditInterfaceFactory(
140
+ reddit_interface_factory = DefaultRedditInterfaceFactory(
148
141
  bot_credentials
149
142
  )
150
143
  # Handle if credential authentication fails
@@ -154,7 +147,7 @@ class RedditAdminImplementation(RedditAdmin):
154
147
  "Please enter new valid credentials"
155
148
  )
156
149
  try:
157
- new_bot_credentials = self.___get_new_bot_credentials()
150
+ new_bot_credentials = self.__get_new_bot_credentials()
158
151
  reddit_interface_factory = self.__get_reddit_interface_factory(new_bot_credentials)
159
152
  except (KeyboardInterrupt, EOFError):
160
153
  raise BotInitializationError(
@@ -206,6 +199,7 @@ class RedditAdminImplementation(RedditAdmin):
206
199
  self.__pluginsExecutor = self.__initialize_plugins_executor(
207
200
  bot_credentials
208
201
  )
202
+ self.__mainLogger.info("Bot successfully initialized")
209
203
 
210
204
  # -------------------------------------------------------------------------------
211
205
 
@@ -213,13 +207,10 @@ class RedditAdminImplementation(RedditAdmin):
213
207
  except BotInitializationError as er:
214
208
  self.__mainLogger.critical(
215
209
  "A fatal error occurred during the "
216
- "bot's initialization. The application "
217
- "will now exit. Error(s): " + str(er),
210
+ "bot's initialization. Error(s): " + str(er),
218
211
  exc_info=True
219
212
  )
220
- sys.exit(2) # TODO: May need future cleaning up
221
-
222
- self.__mainLogger.info("Bot successfully initialized")
213
+ raise er
223
214
 
224
215
  # -------------------------------------------------------------------------------
225
216
 
@@ -310,21 +301,7 @@ class RedditAdminImplementation(RedditAdmin):
310
301
  "'{}' is not a valid bot command".format(command)
311
302
  )
312
303
 
313
- @staticmethod
314
- def __kill_bot():
315
- """Forcefully shut down the bot"""
316
-
317
- # Windows kill command
318
- if (
319
- sys.platform.startswith('win32') or
320
- sys.platform.startswith('cygwin')
321
- ):
322
- os.kill(os.getpid(), signal.CTRL_BREAK_EVENT)
323
-
324
- # Linux kill command
325
- os.kill(os.getpid(), signal.SIGKILL)
326
-
327
- def __shut_down_bot(self, wait=True, shutdown_exit_code=0):
304
+ def __shut_down_bot(self, wait=True):
328
305
  """Shut down the bot"""
329
306
 
330
307
  if wait:
@@ -342,30 +319,13 @@ class RedditAdminImplementation(RedditAdmin):
342
319
  )
343
320
  )
344
321
  )
345
- try:
346
- self.__pluginsExecutor.shut_down(True)
347
- self.__mainLogger.info('Bot successfully shut down')
348
- if shutdown_exit_code != 0:
349
- sys.exit(shutdown_exit_code)
350
-
351
- # Handle keyboard interrupt midway through graceful shutdown
352
- except KeyboardInterrupt:
353
-
354
- self.__mainLogger.warning(
355
- 'Graceful shutdown aborted.'
356
- )
357
- self.__pluginsExecutor.shut_down(False)
358
- self.__mainLogger.info('Bot shut down')
359
-
360
- # Killing the process (only way to essentially stop all threads)
361
- self.__kill_bot()
322
+ self.__pluginsExecutor.shut_down(True)
323
+ self.__mainLogger.info('Bot successfully shut down')
362
324
 
363
325
  else:
364
326
  self.__pluginsExecutor.shut_down(False)
365
327
  self.__mainLogger.info('Bot shut down')
366
328
 
367
- self.__kill_bot()
368
-
369
329
  def __is_bot_shut_down(self):
370
330
  """Check if bot is shutdown"""
371
331
 
@@ -389,7 +349,7 @@ class RedditAdminImplementation(RedditAdmin):
389
349
  'a graceful shutdown is attempted or press '
390
350
  'Ctrl+C to exit immediately'
391
351
  )
392
- self.__shut_down_bot(True, 1)
352
+ self.__shut_down_bot(True)
393
353
 
394
354
  # Handle unknown exception while bot is running
395
355
  except BaseException as ex:
@@ -399,7 +359,7 @@ class RedditAdminImplementation(RedditAdmin):
399
359
  "a graceful shutdown is attempted or press "
400
360
  "Ctrl+C to exit immediately: " + str(ex.args), exc_info=True
401
361
  )
402
- self.__shut_down_bot(True, 2)
362
+ self.__shut_down_bot(True)
403
363
 
404
364
  def run(self, bot_credentials: BotCredentials, listen: bool = False):
405
365
 
@@ -427,9 +387,9 @@ class RedditAdminImplementation(RedditAdmin):
427
387
  if not self.__is_bot_shut_down():
428
388
  self.__shut_down_bot()
429
389
 
430
- def stop(self):
390
+ def stop(self, wait: bool):
431
391
 
432
- self.__shut_down_bot()
392
+ self.__shut_down_bot(wait=wait)
433
393
 
434
394
  # -------------------------------------------------------------------------------
435
395
 
@@ -437,4 +397,4 @@ class RedditAdminImplementation(RedditAdmin):
437
397
  def get_reddit_admin(plugins: List[Plugin]) -> RedditAdmin:
438
398
  """Get a Reddit Admin instance"""
439
399
 
440
- return RedditAdminImplementation(plugins=plugins)
400
+ return _RedditAdminImplementation(plugins=plugins)
@@ -1,3 +1,5 @@
1
- from .plugin import Plugin
2
- from .pluginsexecutor import PluginsExecutor
3
- from .redditinterfacefactory import RedditInterfaceFactory
1
+ from .plugin import Plugin as IPlugin, AbstractPlugin as Plugin, PluginInitializationError
2
+ from .pluginsexecutor import PluginsExecutor as IPluginsExecutor, \
3
+ AbstractPluginsExecutor as PluginsExecutor, PluginsExecutorInitializationError
4
+ from .redditinterfacefactory import RedditInterfaceFactory as IRedditInterfaceFactory,\
5
+ DefaultRedditInterfaceFactory as RedditInterfaceFactory
@@ -3,22 +3,19 @@
3
3
  import concurrent.futures
4
4
  from concurrent.futures import ThreadPoolExecutor, Future
5
5
  from typing import Dict, List
6
- from .pluginsexecutor import PluginsExecutor
6
+ from .pluginsexecutor import AbstractPluginsExecutor, PluginsExecutorInitializationError
7
7
  from .redditinterfacefactory import RedditInterfaceFactory
8
8
  from .plugin import Plugin
9
- from .exceptions import PluginsExecutorInitializationError
10
9
 
11
10
 
12
- class AsynchronousPluginsExecutor(PluginsExecutor):
11
+ class AsynchronousPluginsExecutor(AbstractPluginsExecutor):
13
12
  """
14
- Class responsible for asynchronously executing
15
- multiple plugins in different threads
13
+ Executes multiple plugins in different threads
16
14
  """
17
15
 
18
16
  __executor: ThreadPoolExecutor
19
17
  __plugins: Dict[str, Plugin]
20
18
  __executedPrograms: Dict[str, Future]
21
- __redditInterfaceFactory: RedditInterfaceFactory
22
19
 
23
20
  def __init__(
24
21
  self,
@@ -182,7 +179,6 @@ class AsynchronousPluginsExecutor(PluginsExecutor):
182
179
  )
183
180
 
184
181
  def get_program_statuses(self):
185
- """Get the executed program statuses"""
186
182
 
187
183
  program_statuses = \
188
184
  {
@@ -192,7 +188,6 @@ class AsynchronousPluginsExecutor(PluginsExecutor):
192
188
  return program_statuses
193
189
 
194
190
  def shut_down(self, wait):
195
- """Shut down the plugin executor"""
196
191
 
197
192
  super().shut_down()
198
193
  for plugin in self.__plugins.values():
@@ -1,20 +1,42 @@
1
1
  import logging
2
- from abc import ABC, abstractmethod
2
+ from abc import ABCMeta, abstractmethod
3
3
  from typing import TypeVar, Generic
4
4
 
5
5
  from ..program.program import Program
6
6
  from ..utility.redditinterface import RedditInterface
7
- from ..utility.exceptions import InitializationError
7
+ from ..utility.miscellaneous import InitializationError
8
8
 
9
9
  T = TypeVar("T", bound=Program)
10
10
 
11
11
 
12
- class Plugin(Generic[T], ABC):
12
+ class Plugin(Generic[T], metaclass=ABCMeta):
13
13
  """
14
- Class responsible for generating multiple
15
- instances of a specific program
14
+ Generates multiple instances of a specific program
16
15
  """
17
16
 
17
+ @abstractmethod
18
+ def get_program(self, reddit_interface: RedditInterface) -> T:
19
+ """Get new program instance"""
20
+ ...
21
+
22
+ @abstractmethod
23
+ def get_program_command(self) -> str:
24
+ """Get the program command string"""
25
+ ...
26
+
27
+ @abstractmethod
28
+ def is_shut_down(self) -> bool:
29
+ """Check if plugin is shut down"""
30
+ ...
31
+
32
+ @abstractmethod
33
+ def shut_down(self):
34
+ """Shut down the plugin"""
35
+ ...
36
+
37
+
38
+ class AbstractPlugin(Generic[T], Plugin, metaclass=ABCMeta):
39
+
18
40
  _programCommand: str
19
41
  _pluginLogger: logging.Logger
20
42
  _isPluginShutDown: bool
@@ -29,33 +51,23 @@ class Plugin(Generic[T], ABC):
29
51
  )
30
52
  self._isPluginShutDown = False
31
53
 
32
- @abstractmethod
33
- def get_program(self, reddit_interface: RedditInterface) -> T:
34
- """Get new program instance"""
35
-
36
- raise NotImplementedError
37
-
38
54
  def get_program_command(self) -> str:
39
- """Get the program command string"""
40
55
  return self._programCommand
41
56
 
42
57
  def is_shut_down(self) -> bool:
43
- """Check if plugin is shut down"""
44
58
  return self._isPluginShutDown
45
59
 
46
60
  def shut_down(self):
47
- """Shut down the plugin"""
48
61
  self._isPluginShutDown = True
49
62
 
50
63
  def __eq__(self, value) -> bool:
51
- return isinstance(value, Plugin) and \
64
+ return isinstance(value, AbstractPlugin) and \
52
65
  self.get_program_command() == value.get_program_command()
53
66
 
54
67
 
55
68
  class PluginInitializationError(InitializationError):
56
69
  """
57
- Class to encapsulate an error in the initialization
58
- of a plugin module
70
+ Raised when initialization of a plugin module fails
59
71
  """
60
72
 
61
73
  def __init__(self, *args):
@@ -1,43 +1,54 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  import logging
4
- from abc import ABC, abstractmethod
4
+ from abc import ABCMeta, abstractmethod
5
5
  from typing import Dict
6
6
 
7
+ from src.redditadmin.utility.miscellaneous import InitializationError
7
8
 
8
- class PluginsExecutor(ABC):
9
+
10
+ class PluginsExecutor(metaclass=ABCMeta):
9
11
  """
10
- Class responsible for executing plugins
12
+ Executes plugins
11
13
  """
12
14
 
13
- _isPluginsExecutorShutDown: bool
14
- _pluginsExecutorLogger: logging.Logger
15
-
16
- def __init__(self, plugins_executor_name: str):
17
- self._pluginsExecutorLogger = logging.getLogger(
18
- plugins_executor_name
19
- )
20
- self._isPluginsExecutorShutDown = False
21
-
22
15
  @abstractmethod
23
16
  def execute_program(self, program_command):
24
17
  """Execute the provided program command"""
25
-
26
- raise NotImplementedError
18
+ ...
27
19
 
28
20
  @abstractmethod
29
21
  def get_program_statuses(self) -> Dict[str, str]:
30
22
  """Get the executed program statuses"""
23
+ ...
31
24
 
32
- raise NotImplementedError
33
-
25
+ @abstractmethod
34
26
  def shut_down(self, *args):
35
27
  """Shut down the plugins executor"""
28
+ ...
29
+
30
+ @abstractmethod
31
+ def is_shut_down(self) -> bool:
32
+ """Check if the Plugins Executor is shut down"""
33
+ ...
34
+
35
+
36
+ class AbstractPluginsExecutor(PluginsExecutor, metaclass=ABCMeta):
37
+
38
+ _isPluginsExecutorShutDown: bool
39
+ _pluginsExecutorLogger: logging.Logger
40
+
41
+ def __init__(self, plugins_executor_name: str):
42
+ self._pluginsExecutorLogger = logging.getLogger(
43
+ plugins_executor_name
44
+ )
45
+ self._isPluginsExecutorShutDown = False
46
+
47
+ def shut_down(self, *args):
36
48
 
37
49
  self._isPluginsExecutorShutDown = True
38
50
 
39
51
  def is_shut_down(self) -> bool:
40
- """Check if the Plugins Executor is shut down"""
41
52
 
42
53
  return self._isPluginsExecutorShutDown
43
54
 
@@ -52,3 +63,12 @@ class PluginsExecutor(ABC):
52
63
  "The plugins executor cannot execute any more program "
53
64
  "after it has been shut down"
54
65
  )
66
+
67
+
68
+ class PluginsExecutorInitializationError(InitializationError):
69
+ """
70
+ Raised when initialization of a Plugins Executor module fails
71
+ """
72
+
73
+ def __init__(self, *args):
74
+ super().__init__(*args)
@@ -1,49 +1,47 @@
1
+ from abc import ABCMeta, abstractmethod
2
+
1
3
  import praw
2
4
 
3
- from ..utility.botcredentials import BotCredentials
4
- from ..utility.exceptions import InvalidBotCredentialsError
5
- from ..utility.redditinterface import RedditInterface
5
+ from ..utility.botcredentials import BotCredentials, InvalidBotCredentialsError
6
+ from ..utility.miscellaneous import is_reddit_authenticated
7
+ from ..utility.redditinterface import RedditInterface, RedditInterfaceImplementation
6
8
 
7
9
 
8
- class RedditInterfaceFactory:
10
+ class RedditInterfaceFactory(metaclass=ABCMeta):
9
11
  """Factory for RedditInterface objects"""
10
12
 
11
- __botCredentials: BotCredentials
13
+ @abstractmethod
14
+ def get_reddit_interface(self) -> RedditInterface:
15
+ """Retrieve new Reddit Interface"""
16
+ ...
17
+
18
+
19
+ class DefaultRedditInterfaceFactory(RedditInterfaceFactory):
12
20
 
13
21
  def __init__(
14
22
  self,
15
23
  bot_credentials: BotCredentials
16
24
  ):
17
25
  praw_reddit = praw.Reddit(
18
- user_agent=bot_credentials.get_user_agent,
19
- client_id=bot_credentials.get_client_id,
20
- client_secret=bot_credentials.get_client_secret,
21
- username=bot_credentials.getusername,
22
- password=bot_credentials.get_password
26
+ user_agent=bot_credentials.user_agent,
27
+ client_id=bot_credentials.client_id,
28
+ client_secret=bot_credentials.client_secret,
29
+ username=bot_credentials.username,
30
+ password=bot_credentials.password
23
31
  )
24
- if not self.__authenticated(praw_reddit):
32
+ if not is_reddit_authenticated(praw_reddit):
25
33
  raise InvalidBotCredentialsError
26
34
 
27
35
  self.__botCredentials = bot_credentials
28
36
 
29
- @staticmethod
30
- def __authenticated(praw_reddit_instance: praw.Reddit) -> bool:
31
- """
32
- Convenience method to authenticate bot credentials
33
- provided to Reddit instance
34
- """
35
-
36
- return not praw_reddit_instance.read_only
37
-
38
- def get_reddit_interface(self) -> RedditInterface:
39
- """Retrieve new Reddit Interface"""
37
+ def get_reddit_interface(self):
40
38
 
41
39
  bot_credentials = self.__botCredentials
42
40
  praw_reddit = praw.Reddit(
43
- user_agent=bot_credentials.get_user_agent,
44
- client_id=bot_credentials.get_client_id,
45
- client_secret=bot_credentials.get_client_secret,
46
- username=bot_credentials.getusername,
47
- password=bot_credentials.get_password
41
+ user_agent=bot_credentials.user_agent,
42
+ client_id=bot_credentials.client_id,
43
+ client_secret=bot_credentials.client_secret,
44
+ username=bot_credentials.username,
45
+ password=bot_credentials.password
48
46
  )
49
- return RedditInterface(praw_reddit)
47
+ return RedditInterfaceImplementation(praw_reddit)
@@ -1,3 +1,5 @@
1
- from .program import Program, RecurringProgram
2
- from .streamprocessingprogram import StreamProcessingProgram, StreamFactory, SubmissionStreamFactory, \
3
- CommentStreamFactory, CustomStreamFactory
1
+ from .program import Program as IProgram, AbstractProgram as Program,\
2
+ RecurringProgram as IRecurringProgram, AbstractRecurringProgram as RecurringProgram
3
+ from .streamprocessingprogram import StreamProcessingProgram as IStreamProcessingProgram,\
4
+ AbstractStreamProcessingProgram as StreamProcessingProgram,\
5
+ StreamFactory, SubmissionStreamFactory, CommentStreamFactory, CustomStreamFactory
@@ -1,26 +1,45 @@
1
1
  import logging
2
2
  import time
3
- from abc import ABC, abstractmethod
3
+ from abc import ABCMeta, abstractmethod
4
4
  from typing import Callable
5
5
 
6
6
 
7
- class Program(ABC):
8
- """Class representing a simple program"""
7
+ class Program(metaclass=ABCMeta):
8
+ """Encapsulates a simple program/script"""
9
9
 
10
- def __init__(self, program_name: str):
11
- self._programLogger = logging.getLogger(
12
- program_name
13
- )
10
+ @property
11
+ @abstractmethod
12
+ def program_name(self):
13
+ ...
14
14
 
15
15
  @abstractmethod
16
16
  def execute(self, *args, **kwargs):
17
17
  """Execute the program"""
18
+ ...
18
19
 
19
- raise NotImplementedError()
20
20
 
21
+ class RecurringProgram(Program, metaclass=ABCMeta):
22
+ """Encapsulates a looping program type"""
21
23
 
22
- class RecurringProgram(Program, ABC):
23
- """Class encapsulating a looping program type"""
24
+ @abstractmethod
25
+ def _run_nature_core(self, *args, **kwargs):
26
+ """Run core program"""
27
+ ...
28
+
29
+
30
+ class AbstractProgram(Program, metaclass=ABCMeta):
31
+
32
+ def __init__(self, program_name: str):
33
+ self.__program_name = program_name
34
+ self._programLogger = logging.getLogger(
35
+ program_name
36
+ )
37
+
38
+ def program_name(self):
39
+ return self.__program_name
40
+
41
+
42
+ class AbstractRecurringProgram(RecurringProgram, AbstractProgram, metaclass=ABCMeta):
24
43
 
25
44
  def __init__(
26
45
  self,
@@ -28,7 +47,7 @@ class RecurringProgram(Program, ABC):
28
47
  stop_condition: Callable[..., bool],
29
48
  cooldown: float = 0
30
49
  ):
31
- super().__init__(program_name)
50
+ super().__init__(program_name=program_name)
32
51
  self._stopCondition = stop_condition
33
52
  self._cooldown = cooldown
34
53
 
@@ -37,9 +56,3 @@ class RecurringProgram(Program, ABC):
37
56
  self._run_nature_core(*args, **kwargs)
38
57
  if self._cooldown and self._cooldown > 0:
39
58
  time.sleep(self._cooldown)
40
-
41
- @abstractmethod
42
- def _run_nature_core(self, *args, **kwargs):
43
- """Run core program"""
44
-
45
- raise NotImplementedError()
@@ -1,39 +1,47 @@
1
- from abc import ABC, abstractmethod
1
+ from abc import ABCMeta, abstractmethod
2
2
  from typing import Generator, Callable
3
3
 
4
4
  from praw.models import Subreddit, ListingGenerator
5
5
  from praw.models.util import stream_generator
6
6
 
7
- from .program import RecurringProgram
7
+ from .program import RecurringProgram, AbstractRecurringProgram
8
8
  from ..utility.decorators import consumestransientapierrors
9
9
 
10
10
 
11
- class StreamFactory(ABC):
11
+ class StreamFactory(metaclass=ABCMeta):
12
12
  """
13
- Class responsible for producing
14
- new Reddit Object streams at request
13
+ Produces new Reddit Object streams at request
15
14
  """
16
15
 
17
16
  @abstractmethod
18
17
  def get_new_stream(self) -> Generator:
19
18
  """Produce new stream"""
19
+ ...
20
20
 
21
- raise NotImplementedError()
22
21
 
23
-
24
- class StreamProcessingProgram(RecurringProgram, ABC):
22
+ class StreamProcessingProgram(RecurringProgram, metaclass=ABCMeta):
25
23
  """
26
- Class encapsulating a stream processing
27
- program
24
+ Encapsulates a stream processing program
28
25
  """
29
26
 
27
+ @abstractmethod
28
+ def _run_pause_handler(self, *args):
29
+ """Execute when stream is paused"""
30
+ ...
31
+
32
+
33
+ class AbstractStreamProcessingProgram(StreamProcessingProgram, AbstractRecurringProgram, metaclass=ABCMeta):
34
+
30
35
  def __init__(
31
36
  self,
32
37
  stream_factory: StreamFactory,
33
38
  stop_condition: Callable[..., bool],
34
39
  program_name: str
35
40
  ):
36
- super().__init__(program_name, stop_condition)
41
+ super().__init__(
42
+ program_name=program_name,
43
+ stop_condition=stop_condition
44
+ )
37
45
  self.__streamFactory = stream_factory
38
46
 
39
47
  @consumestransientapierrors
@@ -60,15 +68,10 @@ class StreamProcessingProgram(RecurringProgram, ABC):
60
68
 
61
69
  self._run_nature_core(streamItem)
62
70
 
63
- def _run_pause_handler(self, *args):
64
- """Execute when stream is paused"""
65
- pass
66
-
67
71
 
68
72
  class SubmissionStreamFactory(StreamFactory):
69
73
  """
70
- Class responsible for producing
71
- new Submission streams at request
74
+ Produces new Submission streams at request
72
75
  """
73
76
 
74
77
  def __init__(
@@ -91,8 +94,7 @@ class SubmissionStreamFactory(StreamFactory):
91
94
 
92
95
  class CommentStreamFactory(StreamFactory):
93
96
  """
94
- Class responsible for producing
95
- new Comment streams at request
97
+ Produces new Comment streams at request
96
98
  """
97
99
 
98
100
  def __init__(
@@ -115,8 +117,7 @@ class CommentStreamFactory(StreamFactory):
115
117
 
116
118
  class CustomStreamFactory(StreamFactory):
117
119
  """
118
- Class responsible for producing new
119
- stream of custom Reddit objects according to
120
+ Produces new stream of custom Reddit objects according to
120
121
  the provided Listing Generator
121
122
  """
122
123
 
@@ -1,5 +1,9 @@
1
- from .botcredentials import BotCredentials
2
- from .contributionsutility import retrieve_submissions_from_subreddit, retrieve_select_submissions, is_removed
1
+ from .botcredentials import BotCredentials as IBotCredentials,\
2
+ BotCredentialsImplementation as BotCredentials, InvalidBotCredentialsError
3
+ from .contributions import retrieve_submissions_from_subreddit, retrieve_select_submissions, is_removed
3
4
  from .decorators import consumestransientapierrors
4
- from .redditinterface import RedditInterface
5
- from .redditsubmission import RedditSubmission
5
+ from .miscellaneous import is_reddit_authenticated, BotInitializationError, InitializationError
6
+ from .redditinterface import RedditInterface as IRedditInterface,\
7
+ RedditInterfaceImplementation as RedditInterface
8
+ from .redditsubmission import RedditSubmission as IRedditSubmission,\
9
+ RedditSubmissionImplementation as RedditSubmission
@@ -1,13 +1,43 @@
1
- class BotCredentials:
1
+ from abc import ABCMeta, abstractmethod
2
+
3
+
4
+ class BotCredentials(metaclass=ABCMeta):
2
5
  """
3
- Class holding the bot's credentials
6
+ Encapsulates the bot's credentials
4
7
  """
5
8
 
6
- __user_agent: str
7
- __client_id: str
8
- __client_secret: str
9
- __username: str
10
- __password: str
9
+ @property
10
+ @abstractmethod
11
+ def password(self) -> str:
12
+ """Retrieve the bot's Password"""
13
+ ...
14
+
15
+ @property
16
+ @abstractmethod
17
+ def client_secret(self) -> str:
18
+ """Retrieve the bot's Client Secret"""
19
+ ...
20
+
21
+ @property
22
+ @abstractmethod
23
+ def client_id(self) -> str:
24
+ """Retrieve the bot's Client ID"""
25
+ ...
26
+
27
+ @property
28
+ @abstractmethod
29
+ def user_agent(self) -> str:
30
+ """Retrieve the bot's User Agent"""
31
+ ...
32
+
33
+ @property
34
+ @abstractmethod
35
+ def username(self) -> str:
36
+ """Retrieve the bot's Username"""
37
+ ...
38
+
39
+
40
+ class BotCredentialsImplementation(BotCredentials):
11
41
 
12
42
  def __init__(
13
43
  self,
@@ -24,40 +54,30 @@ class BotCredentials:
24
54
  self.__password = password
25
55
 
26
56
  @property
27
- def get_user_agent(self):
28
- """Retrieve the bot's User Agent"""
29
-
57
+ def user_agent(self) -> str:
30
58
  return self.__user_agent
31
59
 
32
60
  @property
33
- def get_client_id(self):
34
- """Retrieve the bot's Client ID"""
35
-
61
+ def client_id(self):
36
62
  return self.__client_id
37
63
 
38
64
  @property
39
- def get_client_secret(self):
40
- """Retrieve the bot's Client Secret"""
41
-
65
+ def client_secret(self):
42
66
  return self.__client_secret
43
67
 
44
68
  @property
45
- def getusername(self):
46
- """Retrieve the bot's Username"""
47
-
69
+ def username(self):
48
70
  return self.__username
49
71
 
50
72
  @property
51
- def get_password(self):
52
- """Retrieve the bot's Password"""
53
-
73
+ def password(self):
54
74
  return self.__password
55
75
 
56
- def clear_credentials(self):
57
- """Convenience method to clear the bot's credentials"""
58
76
 
59
- self.__user_agent = ""
60
- self.__client_id = ""
61
- self.__client_secret = ""
62
- self.__username = ""
63
- self.__password = ""
77
+ class InvalidBotCredentialsError(Exception):
78
+ """
79
+ Raised when provided bot credentials are invalid
80
+ """
81
+
82
+ def __init__(self, *args):
83
+ super().__init__(self, args)
@@ -61,4 +61,4 @@ def is_removed(
61
61
  except AttributeError:
62
62
  author = None
63
63
  return author is None or author == '[Deleted]' or \
64
- contribution.banned_by is not None
64
+ contribution.banned_by is not None
@@ -11,10 +11,9 @@ from prawcore.exceptions import RequestException, ServerError
11
11
 
12
12
  def consumestransientapierrors(_execute_function=None, *, timeout: int = 30):
13
13
  """
14
- Decorator responsible for consuming common transient
15
- errors which may occur while connecting to the
16
- Reddit API during the running of the provided
17
- program
14
+ Consumes common transient errors which may
15
+ occur while connecting to the Reddit API during
16
+ the running of the provided program
18
17
  """
19
18
 
20
19
  def subsuming_function(execute_function):
@@ -0,0 +1,28 @@
1
+ import praw
2
+
3
+
4
+ class InitializationError(Exception):
5
+ """
6
+ Raised when the initialization of a module fails
7
+ """
8
+
9
+ def __init__(self, *args):
10
+ super().__init__(self, args)
11
+
12
+
13
+ class BotInitializationError(InitializationError):
14
+ """
15
+ Raised when the initialization of a bot module fails
16
+ """
17
+
18
+ def __init__(self, *args):
19
+ super().__init__(*args)
20
+
21
+
22
+ def is_reddit_authenticated(praw_reddit_instance: praw.Reddit) -> bool:
23
+ """
24
+ Convenience method to authenticate bot credentials
25
+ provided to Reddit instance
26
+ """
27
+
28
+ return not praw_reddit_instance.read_only
@@ -1,19 +1,27 @@
1
1
  # -*- coding: utf-8 -*
2
+ from abc import ABCMeta, abstractmethod
3
+
2
4
  from praw import Reddit
3
5
 
4
6
 
5
- class RedditInterface:
7
+ class RedditInterface(metaclass=ABCMeta):
6
8
  """
7
- Class holding tools to interface with the Reddit API
9
+ Encapsulates tools to interface with the Reddit API
8
10
  """
9
11
 
10
- __prawReddit: Reddit
12
+ @property
13
+ @abstractmethod
14
+ def praw_reddit(self):
15
+ """Retrieve the interface's PrawReddit instance"""
16
+ ...
17
+
18
+
19
+ class RedditInterfaceImplementation(RedditInterface):
11
20
 
12
21
  def __init__(self, praw_reddit: Reddit):
13
22
  self.__prawReddit = praw_reddit
14
23
 
15
24
  @property
16
- def get_praw_reddit(self):
17
- """Retrieve the interface's PrawReddit instance"""
25
+ def praw_reddit(self):
18
26
 
19
27
  return self.__prawReddit
@@ -1,18 +1,26 @@
1
+ from abc import abstractmethod, ABCMeta
2
+
1
3
  from praw.models import Submission
2
4
 
3
5
 
4
- class RedditSubmission:
6
+ class RedditSubmission(metaclass=ABCMeta):
5
7
  """
6
- Class encapsulating a submission
8
+ Encapsulates a submission
7
9
  """
8
10
 
9
- __submissionId: str
11
+ @property
12
+ @abstractmethod
13
+ def submission_id(self):
14
+ ...
15
+
16
+
17
+ class RedditSubmissionImplementation(RedditSubmission):
10
18
 
11
19
  def __init__(self, submission_id):
12
20
  self.__submissionId = submission_id
13
21
 
14
22
  @property
15
- def get_submission_id(self):
23
+ def submission_id(self):
16
24
  return self.__submissionId
17
25
 
18
26
  @classmethod
@@ -20,17 +28,17 @@ class RedditSubmission:
20
28
  cls, submission_id: str
21
29
  ):
22
30
  """
23
- Returns a SimpleSubmission object from
24
- the provided submissionId
31
+ Returns a RedditSubmission object from
32
+ the provided submission_id
25
33
  """
26
- return RedditSubmission(submission_id)
34
+ return RedditSubmissionImplementation(submission_id)
27
35
 
28
36
  @classmethod
29
37
  def get_submission_from_praw_submission(
30
38
  cls, praw_submission: Submission
31
39
  ):
32
40
  """
33
- Returns a SimpleSubmission object from
41
+ Returns a RedditSubmission object from
34
42
  the provided PRAW submission
35
43
  """
36
- return RedditSubmission(praw_submission.id)
44
+ return RedditSubmissionImplementation(praw_submission.id)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redditadmin
3
- Version: 0.0.6
3
+ Version: 0.1.1
4
4
  Summary: Extensible Python administrative bot
5
5
  Project-URL: Homepage, https://github.com/Grod56/reddit-admin
6
6
  Project-URL: Issues, https://github.com/Grod56/reddit-admin/issues
@@ -130,17 +130,21 @@ Plugin:
130
130
 
131
131
  ```py
132
132
  class MyPlugin[Plugin[MyProgram]]
133
- """The plugin for our program"""
134
133
 
135
- def __init__():
136
- super().__init__("myprogram")
137
134
 
138
- def get_program(self, reddit_interface):
139
- """Overriden from Plugin superclass"""
135
+ """The plugin for our program"""
140
136
 
141
- praw_reddit = reddit_interface.get_praw_reddit
142
137
 
143
- return MyProgram(praw_reddit)
138
+ def __init__():
139
+ super().__init__("myprogram")
140
+
141
+
142
+ def get_program(self, reddit_interface):
143
+ """Overriden from Plugin superclass"""
144
+
145
+ praw_reddit = reddit_interface.praw_reddit
146
+
147
+ return MyProgram(praw_reddit)
144
148
  ```
145
149
 
146
150
  Voila! We may now supply our plugin to the bot and run it just as we did before
@@ -0,0 +1,21 @@
1
+ redditadmin/__init__.py,sha256=H9tJpqCduGTfE__euVT9Qp5vWzS6VI3iXmGfbl_cixw,120
2
+ redditadmin/core.py,sha256=eV_5JAZ_KvqVDxRkqrzzMumVkxs8SZecsiv3j9P5d20,14012
3
+ redditadmin/plugin/__init__.py,sha256=aFjHxkbg-7X3NTdl0TeVWIbtkc-Efzz2iZPvV--UdNg,395
4
+ redditadmin/plugin/asynchronouspluginsexecutor.py,sha256=n83iXNygrn8l0DnkEt0jb1UbOV1VtzxTV7OD3sJDsuQ,6897
5
+ redditadmin/plugin/plugin.py,sha256=qJFmmruUn-CiKZcw6Vl13EpHm1bAd9D0CwKnqqDsYoU,1906
6
+ redditadmin/plugin/pluginsexecutor.py,sha256=5iBcYDsk_MEN5DdO95T_Lb3nDwF1pBkRKoddmAY-jF4,1948
7
+ redditadmin/plugin/redditinterfacefactory.py,sha256=QIyN0PIuzEAMPAlxD58UE6MMpGxMM08Xb_V9rCtkrJU,1587
8
+ redditadmin/program/__init__.py,sha256=Vu_AxIUS4U2-6dAIGlzeQNvLtS1mY_5wDxUB5xGVV_k,406
9
+ redditadmin/program/program.py,sha256=rT_3wV3w1F9R9voBsZsqYwMDGSEVsHJT_M_EMOxkAS0,1524
10
+ redditadmin/program/streamprocessingprogram.py,sha256=DVWURQiQNtGPOlsiCDG5Abmw3_GvAPKEGJI19AtUcP8,3995
11
+ redditadmin/utility/__init__.py,sha256=7-MxaEvDEoozvwWO56r75wgabuKQuI4jtdl2JhrmRFE,648
12
+ redditadmin/utility/botcredentials.py,sha256=g9wkkPy2XUe7zDV6Mtzpxljr7BclsjPzbu2W6Uiy7J4,1801
13
+ redditadmin/utility/contributions.py,sha256=dPU0OA0PBxh5ktHJtiLizYM5xtqPd-9VhOzRkvdw4iI,1490
14
+ redditadmin/utility/decorators.py,sha256=yk2ZQXA2sLRBIZRl8nvHzeyM5DjMYHwtCZcnNOz2wxY,1629
15
+ redditadmin/utility/miscellaneous.py,sha256=8FaWN6RyxGqCvM8Y1_Ud09mQMll8TpVU9WyYe-9np0w,632
16
+ redditadmin/utility/redditinterface.py,sha256=Nd9cZEyS0tRtJWpy_QO0TsfQzG5LK_K5hSokl3Hu2IA,582
17
+ redditadmin/utility/redditsubmission.py,sha256=SshPTHHCFLrBOMudKNc8P_kn9oVXUdbyxwnaV54xQnE,1057
18
+ redditadmin-0.1.1.dist-info/METADATA,sha256=HUJrOaTZJAgquFOkm-YpB3djcD6pggQweUVYZVxK4t4,5338
19
+ redditadmin-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
20
+ redditadmin-0.1.1.dist-info/licenses/LICENSE,sha256=kieFEKjHWxFgNnmvKA_eXHmaQQZ9ZgEI_5m14IiVzAo,1091
21
+ redditadmin-0.1.1.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- from ..utility.exceptions import InitializationError
2
-
3
-
4
- class PluginsExecutorInitializationError(InitializationError):
5
- """
6
- Class to encapsulate an error in the initialization
7
- of a Plugins Executor module
8
- """
9
-
10
- def __init__(self, *args):
11
- super().__init__(*args)
@@ -1,28 +0,0 @@
1
- class InitializationError(Exception):
2
- """
3
- Class to encapsulate an error in the
4
- initialization of a module
5
- """
6
-
7
- def __init__(self, *args):
8
- super().__init__(self, args)
9
-
10
-
11
- class BotInitializationError(InitializationError):
12
- """
13
- Class to encapsulate an error in the
14
- initialization of a bot module
15
- """
16
-
17
- def __init__(self, *args):
18
- super().__init__(*args)
19
-
20
-
21
- class InvalidBotCredentialsError(Exception):
22
- """
23
- Class encapsulating an exception raised
24
- when provided bot credentials are invalid
25
- """
26
-
27
- def __init__(self, *args):
28
- super().__init__(self, args)
@@ -1,22 +0,0 @@
1
- redditadmin/__init__.py,sha256=H9tJpqCduGTfE__euVT9Qp5vWzS6VI3iXmGfbl_cixw,120
2
- redditadmin/core.py,sha256=iGgN74nYgRLvrkAue9hk-Ptdmpr0oUCx8xWdW5ROBaY,15173
3
- redditadmin/plugin/__init__.py,sha256=qHKyhBz5ovgef0lcXtIUsP7ZbryDrXCxRylyGJJjgTo,134
4
- redditadmin/plugin/asynchronouspluginsexecutor.py,sha256=WkEbTFciqgJ2cQ59AgXJBsPpPntugJxF2KapB1NNmwY,7096
5
- redditadmin/plugin/exceptions.py,sha256=tNwWiitjeCXVFTL9_y3fXF_Dzjk-CUHd2uqvl0_UUmI,298
6
- redditadmin/plugin/plugin.py,sha256=_Zr1mhTP6y1cZDxxd8vcRYodSeiOViprmZ7TGJwaA6A,1673
7
- redditadmin/plugin/pluginsexecutor.py,sha256=i758rqdlFysJh63ZzVY3fqQJriH2hc-gubiuK9wa24Y,1496
8
- redditadmin/plugin/redditinterfacefactory.py,sha256=20RInQUmvHFnbE7-SttjfYa1dsRTD0lgdh5mQVieFUw,1654
9
- redditadmin/program/__init__.py,sha256=86ezrveWRark-dpsSjuD_dPvsxDbh7uqHE-Porlhd_E,200
10
- redditadmin/program/program.py,sha256=pGvx03PWkfQsfdRfQ-g5DbXSG0NjDuGQBrTU3baQJaE,1205
11
- redditadmin/program/streamprocessingprogram.py,sha256=r-VuHmNdEiJq6sAoDDUub-VlX04lVCjgYCBfiiw9gPE,3888
12
- redditadmin/utility/__init__.py,sha256=bZlqoWdMCmR7COJiiSY6RamzyqQSvbLHyifdvrBW4Ac,302
13
- redditadmin/utility/botcredentials.py,sha256=qkrKtqXvA0h1NSwy70ESxsZyyXwXPiB5USpMt-56UwY,1462
14
- redditadmin/utility/contributionsutility.py,sha256=FHOlMJE3Vdk5H2j-_b0TzGP9rhGn-tAxQqx4_-hk8mg,1492
15
- redditadmin/utility/decorators.py,sha256=T0Mir1zX3_tHVDuU3UaQ1Mg1ayapaL84_GSbuifg5lY,1661
16
- redditadmin/utility/exceptions.py,sha256=8XliprLrcYtKYji6JPYw6nfC3yA0i551iOSV3Op06A4,652
17
- redditadmin/utility/redditinterface.py,sha256=kWmUrdQffujf3j9bDE9wCH3FAmvckeaOWcl9NV3MRy0,415
18
- redditadmin/utility/redditsubmission.py,sha256=tB830FGKmnFMBg5GXrMcGKYdGtSSYgt_uCCaLnNYlC4,862
19
- redditadmin-0.0.6.dist-info/METADATA,sha256=QU7GBJQ8cRPmx2shBPPEUBMRS1sC7MmEFQ-Jy0-1k1Q,5366
20
- redditadmin-0.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- redditadmin-0.0.6.dist-info/licenses/LICENSE,sha256=kieFEKjHWxFgNnmvKA_eXHmaQQZ9ZgEI_5m14IiVzAo,1091
22
- redditadmin-0.0.6.dist-info/RECORD,,