Skip to content

Commit 98b8ef2

Browse files
committed
Code cleanups.
1 parent c34e956 commit 98b8ef2

File tree

29 files changed

+182
-99
lines changed

29 files changed

+182
-99
lines changed

core/data/dataobject.py

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from configparser import ConfigParser
66
from dataclasses import dataclass, field
7-
from typing import TYPE_CHECKING, Callable, Type, Optional, TypeVar, Generic
7+
from typing import TYPE_CHECKING, Callable, Type, Optional, TypeVar, Generic, ClassVar, Any
88

99
if TYPE_CHECKING:
1010
from core import Node
@@ -41,23 +41,73 @@ def __hash__(self):
4141

4242

4343
class DataObjectFactory(Generic[T]):
44-
_instance: Optional[DataObjectFactory] = None
45-
_registry: dict[Type[T], Type[T]] = {}
46-
47-
def __new__(cls) -> DataObjectFactory:
44+
"""
45+
A singleton factory for creating data objects with registration support.
46+
47+
This class provides a central registry for data object types and a factory
48+
method for instantiating them. It uses a singleton pattern to ensure a single
49+
instance is shared across the application.
50+
51+
Type Parameters:
52+
T: The base type for objects created by this factory
53+
54+
Attributes:
55+
_instance: The singleton instance of this class
56+
_registry: Dictionary mapping object types to their implementation classes
57+
"""
58+
_instance: Optional['DataObjectFactory[T]'] = None
59+
# Using class variable storage that's independent of the generic type parameter
60+
_registry: ClassVar[dict[Any, Any]] = {}
61+
62+
def __new__(cls) -> 'DataObjectFactory[T]':
63+
"""
64+
Creates a singleton instance of the factory.
65+
66+
Returns:
67+
The singleton instance of DataObjectFactory
68+
"""
4869
if cls._instance is None:
4970
cls._instance = super(DataObjectFactory, cls).__new__(cls)
5071
return cls._instance
5172

5273
@classmethod
5374
def register(cls, t: Optional[Type[T]] = None) -> Callable[[Type[T]], Type[T]]:
75+
"""
76+
Decorator for registering implementation classes with the factory.
77+
78+
Args:
79+
t: Optional type to use as the registration key. If not provided,
80+
the implementation class itself is used as the key.
81+
82+
Returns:
83+
A decorator function that registers the decorated class
84+
85+
Example:
86+
@DataObjectFactory.register(BaseClass)
87+
class Implementation(BaseClass):
88+
pass
89+
"""
90+
5491
def inner_wrapper(wrapped_class: Type[T]) -> Type[T]:
55-
cls._registry[t or wrapped_class] = wrapped_class
92+
DataObjectFactory._registry[t or wrapped_class] = wrapped_class
5693
return wrapped_class
5794

5895
return inner_wrapper
5996

6097
@classmethod
6198
def new(cls, t: Type[T], **kwargs) -> T:
99+
"""
100+
Creates a new instance of the registered implementation class.
101+
102+
Args:
103+
t: The type to instantiate (must be registered)
104+
**kwargs: Arguments to pass to the constructor
105+
106+
Returns:
107+
A new instance of the requested type
108+
109+
Raises:
110+
KeyError: If the requested type is not registered
111+
"""
62112
# noinspection PyArgumentList
63-
return cls._registry[t](**kwargs)
113+
return DataObjectFactory._registry[t](**kwargs)

core/data/impl/nodeimpl.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ async def post_init(self):
159159
self._master = await self.heartbeat()
160160
self.log.info("- Starting as {} ...".format("Single / Master" if self._master else "Agent"))
161161
except (UndefinedTable, InFailedSqlTransaction):
162-
# some master tables have changed, so do the update first
162+
# some master tables have changed, do the update first
163163
self._master = True
164164
if self._master:
165165
try:
@@ -511,7 +511,7 @@ async def upgrade_pending(self) -> bool:
511511
return rc
512512

513513
async def _upgrade(self, conn: psycopg.AsyncConnection):
514-
# We do not want to run an upgrade, if we are on a cloud drive, so just restart in this case
514+
# We do not want to run an upgrade if we are on a cloud drive. Just restart in this case.
515515
if not self.master and self.locals.get('cloud_drive', True):
516516
await self.restart()
517517
elif await self.upgrade_pending():
@@ -1041,7 +1041,7 @@ async def rename_server(self, server: Server, new_name: str):
10411041
await ServiceRegistry.get(BotService).rename_server(server, new_name)
10421042
# update the ServiceBus
10431043
ServiceRegistry.get(ServiceBus).rename_server(server, new_name)
1044-
# change the proxy name for remote servers (local ones will be renamed by ServerImpl)
1044+
# change the proxy name for remote servers (ServerImpl will rename local ones)
10451045
if server.is_remote:
10461046
server.name = new_name
10471047

@@ -1197,7 +1197,7 @@ async def add_instance(self, name: str, *, template: str = "") -> "Instance":
11971197
shutil.copy2(os.path.expandvars(_template.extensions['SRS']['config']),
11981198
os.path.join(instance.home, 'Config', 'SRS.cfg'))
11991199
else:
1200-
# copy default files, if they exist
1200+
# copy default files if they exist
12011201
if os.path.exists(os.path.join(self.config_dir, 'autoexec.cfg')):
12021202
shutil.copy2(os.path.join(self.config_dir, 'autoexec.cfg'),
12031203
os.path.join(instance.home, 'Config'))
@@ -1292,7 +1292,7 @@ def change_instance_in_path(data):
12921292
await asyncio.to_thread(instance.server.stop_observer)
12931293

12941294
try:
1295-
# test rename it, to make sure it works
1295+
# test-rename it to make sure it works
12961296
new_home = os.path.join(os.path.dirname(instance.home), new_name)
12971297
os.rename(instance.home, new_home)
12981298
os.rename(new_home, instance.home)

core/data/impl/serverimpl.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from pathlib import Path
3333
from typing import Optional, TYPE_CHECKING, Union, Any
3434
from watchdog.events import FileSystemEventHandler, FileSystemEvent, FileSystemMovedEvent
35-
from watchdog.observers import Observer
35+
from watchdog.observers import Observer, ObserverType
3636

3737
# ruamel YAML support
3838
from ruamel.yaml import YAML
@@ -86,7 +86,7 @@ def on_deleted(self, event: FileSystemEvent):
8686
if path in missions:
8787
idx = missions.index(path) + 1
8888
asyncio.run_coroutine_threadsafe(self.server.deleteMission(idx), self.loop)
89-
# cache the index of the line to re-add the file at the correct position afterwards,
89+
# cache the index of the line to re-add the file at the correct position afterward
9090
# if a cloud drive did a delete/add instead of a modification
9191
self.deleted[path] = idx
9292
self.log.info(f"=> Mission {os.path.basename(path)[:-4]} deleted from server {self.server.name}.")
@@ -99,7 +99,7 @@ def on_deleted(self, event: FileSystemEvent):
9999
class ServerImpl(Server):
100100
bot: Optional[DCSServerBot] = field(compare=False, init=False)
101101
event_handler: MissionFileSystemEventHandler = field(compare=False, default=None)
102-
observer: Observer = field(compare=False, default=None)
102+
observer: ObserverType = field(compare=False, default=None)
103103

104104
def __post_init__(self):
105105
super().__post_init__()

core/mizfile.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import datetime
34
import importlib
45
import io
56
import logging
@@ -13,7 +14,7 @@
1314
from astral import LocationInfo
1415
from astral.sun import sun
1516
from core import utils
16-
from datetime import datetime, timedelta, date
17+
from datetime import datetime, timedelta
1718
from packaging.version import parse, Version
1819
from timezonefinder import TimezoneFinder
1920
from typing import Union, Optional
@@ -220,12 +221,12 @@ def start_time(self, value: int) -> None:
220221
self.mission['start_time'] = value
221222

222223
@property
223-
def date(self) -> date:
224+
def date(self) -> datetime.date:
224225
value = self.mission['date']
225-
return date(value['Year'], value['Month'], value['Day'])
226+
return datetime.date(value['Year'], value['Month'], value['Day'])
226227

227228
@date.setter
228-
def date(self, value: date) -> None:
229+
def date(self, value: datetime.date) -> None:
229230
self.mission['date'] = {"Day": value.day, "Year": value.year, "Month": value.month}
230231

231232
@property

core/utils/discord.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -911,26 +911,46 @@ def get_all_linked_members(interaction: discord.Interaction) -> list[discord.Mem
911911

912912
class ServerTransformer(app_commands.Transformer):
913913
"""
914-
915-
:class:`ServerTransformer` is a class that is used for transforming and autocompleting servers as a selection for application commands.
916-
917-
.. attribute:: status
918-
919-
An optional attribute that specifies the list of status values to filter the servers by.
920-
921-
:type: list of :class:`Status`
922-
:default: None
923-
924-
:param status: An optional parameter that specifies the list of status values to filter the servers by.
925-
:type status: list of :class:`Status`
926-
914+
A transformer for Discord application commands that handles server selection.
915+
916+
This class converts string inputs into Server objects and provides autocomplete
917+
functionality for server selection in slash commands.
918+
919+
Attributes:
920+
status (list[Status]): Optional list of status values to filter servers by.
921+
maintenance (bool): Optional filter for servers in maintenance mode.
922+
923+
Example:
924+
```python
925+
@app_commands.command(name="restart")
926+
async def restart_server(
927+
interaction: discord.Interaction,
928+
server: app_commands.Transform[Server, ServerTransformer(status=[Status.RUNNING])]
929+
):
930+
# Command will only show running servers in autocomplete
931+
await server.restart()
932+
```
927933
"""
934+
928935
def __init__(self, *, status: list[Status] = None, maintenance: Optional[bool] = None):
929936
super().__init__()
930937
self.status: list[Status] = status
931938
self.maintenance = maintenance
932939

933940
async def transform(self, interaction: discord.Interaction, value: Optional[str]) -> Server:
941+
"""
942+
Converts a server name into a Server object.
943+
944+
Args:
945+
interaction: The interaction context
946+
value: The server name to convert
947+
948+
Returns:
949+
The corresponding Server object
950+
951+
Raises:
952+
app_commands.TransformerError: If server not found
953+
"""
934954
if value:
935955
server = interaction.client.servers.get(value)
936956
if not server:
@@ -940,6 +960,19 @@ async def transform(self, interaction: discord.Interaction, value: Optional[str]
940960
return server
941961

942962
async def autocomplete(self, interaction: discord.Interaction, current: str) -> list[app_commands.Choice[str]]:
963+
"""
964+
Provides server name suggestions for autocomplete.
965+
966+
Filters servers based on status, maintenance mode, and user input.
967+
Only shows servers the user has permission to access.
968+
969+
Args:
970+
interaction: The interaction context
971+
current: Current text input by user
972+
973+
Returns:
974+
List of server name suggestions (max 25)
975+
"""
943976
if not await interaction.command._check_can_run(interaction):
944977
return []
945978
try:

core/utils/helper.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from collections.abc import Mapping
3030
from copy import deepcopy
3131
from croniter import croniter
32-
from datetime import datetime, timedelta, timezone
32+
from datetime import datetime, timedelta, timezone, tzinfo
3333
from difflib import unified_diff
3434
from importlib import import_module
3535
from pathlib import Path
@@ -87,7 +87,7 @@
8787
logger = logging.getLogger(__name__)
8888

8989

90-
def parse_time(time_str: str, tz: datetime.tzinfo = None) -> datetime:
90+
def parse_time(time_str: str, tz: tzinfo = None) -> datetime:
9191
fmt, time_str = ('%H:%M', time_str.replace('24:', '00:')) \
9292
if time_str.find(':') > -1 else ('%H', time_str.replace('24', '00'))
9393
ret = datetime.strptime(time_str, fmt)
@@ -96,7 +96,7 @@ def parse_time(time_str: str, tz: datetime.tzinfo = None) -> datetime:
9696
return ret
9797

9898

99-
def is_in_timeframe(time: datetime, timeframe: str, tz: datetime.tzinfo = None) -> bool:
99+
def is_in_timeframe(time: datetime, timeframe: str, tz: tzinfo = None) -> bool:
100100
"""
101101
Check if a given time falls within a specified timeframe.
102102
@@ -887,7 +887,7 @@ def hash_password(password: str) -> str:
887887
# Generate an 11 character alphanumeric string
888888
key = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(11))
889889

890-
# Create a 32 byte digest using the Blake2b hash algorithm
890+
# Create a 32-byte-digest using the "Blake2b" hash algorithm
891891
# with the password as the input and the key as the key
892892
password_bytes = password.encode('utf-8')
893893
key_bytes = key.encode('utf-8')

core/utils/os.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def is_upnp_available() -> bool:
328328
# No UPnP devices detected on the network.
329329
return False
330330
except Exception:
331-
# An UPnP device was found, but no IGD was found.
331+
# A UPnP device was found, but no IGD was found.
332332
return False
333333

334334

extensions/lardoon/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ MyNode:
1010
# [...]
1111
extensions:
1212
Lardoon:
13-
use_single_process: true # Start one Lardoon process per node (default: true)
1413
cmd: '%USERPROFILE%\Documents\GitHub\lardoon\lardoon.exe'
1514
bind: 0.0.0.0:3113 # IP and port the (single) Lardoon server is listening to
1615
url: https://myfancyhost.com # Alternate hostname to be displayed in your status embed
1716
minutes: 5 # Number of minutes the Lardoon database is updated
17+
use_single_process: true # Start one Lardoon process instead of one per node (default: true)
1818
# [...]
1919
instances:
2020
DCS.release_server:
@@ -28,7 +28,7 @@ MyNode:
2828
minutes: 5 # Optional: Number of minutes the Lardoon database is updated (only needed if use_single_process is false)
2929
tacviewExportPath: 'G:\My Drive\Tacview Files' # Alternative drive for tacview files (default: auto-detect from Tacview)
3030
```
31-
Remember to add some kind of security before exposing services like that to the outside world, with for instance
31+
Remember to add some kind of security before exposing services like that to the outside world, with, for instance,
3232
an nginx reverse proxy.</br>
3333
If you plan to build Lardoon on your own, I'd recommend the fork of [Team LimaKilo](https://github.com/team-limakilo/lardoon).
3434

extensions/loganalyser/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Extension "LogAnalyser"
22
This is a default extension that is loaded in any case. It will scan your dcs.log for errors and react in several ways
33
according to what happened.
4-
Currently these actions are implemented:
4+
Currently, these actions are implemented:
55
- Restart the mission on server unlisting.
66
- Print script errors to the audit channel (if configured).
77

extensions/loganalyser/extension.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ async def script_error(self, idx: int, line: str, match: re.Match):
203203
filename, line_number, error_message = match.groups()
204204
basename = os.path.basename(filename)
205205

206-
# Get the ignore patterns from config, default to an empty list if not present
206+
# Get the ignore-pattern from config. Defaults to an empty list if not present.
207207
ignore_patterns = self.config.get('ignore_files', [])
208208

209209
# Check if the filename matches any of the regex patterns

0 commit comments

Comments
 (0)