Skip to content

Commit 399562d

Browse files
committed
codebase: typeshi integration + gh ctx attribute
1 parent 055352f commit 399562d

File tree

19 files changed

+88
-33
lines changed

19 files changed

+88
-33
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
*.last.json
33
*.locale.last.json
44

5+
# GitBot dev flow
6+
resources/gen/*
7+
58
# PyCharm Stuff
69
.idea/
710

bot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from lib.structs.discord.context import GitBotContext
1616
from lib.structs.discord.bot import GitBot
1717

18-
# all of the configuration is handled inside the class, there is no real need to pass anything here
18+
# all the configuration is handled inside the class, there is no real need to pass anything here
1919
bot = GitBot()
2020

2121

@@ -72,6 +72,7 @@ async def global_check(ctx: GitBotContext) -> bool:
7272
async def before_invoke(ctx: GitBotContext):
7373
if str(ctx.command) not in bot.mgr.env.no_typing_commands:
7474
await ctx.channel.typing()
75+
# ctx.gh overwrites will be handled here
7576

7677

7778
if __name__ == '__main__':

cli/main.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
import sys
33
import click
4+
import typeshi
45
import subprocess
6+
from lib.structs.proxies.dict_proxy import DictProxy
57
from .config import PYTHON_COMMAND_LINE, APP_ROOT_DIR
68
from .scripts import run_help_helper
79

@@ -31,6 +33,17 @@ def dev():
3133
pass
3234

3335

36+
@dev.command('generate-locale-defs')
37+
def generate_locale_defs():
38+
if not os.path.exists('resources/gen/'):
39+
os.makedirs('resources/gen/')
40+
typeshi.save_declaration_module_from_json(
41+
'Locale', 'resources/locale/en.locale.json', 'resources/gen/locale_schema.py',
42+
inherit_cls=DictProxy
43+
)
44+
print(f'Wrote locale schema to resources/gen/locale_schema.py')
45+
46+
3447
@dev.command('update', help='Update the local code using git')
3548
def update():
3649
if sys.platform == 'win32':

cli/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
click==8.1.6
1+
click==8.1.6
2+
typeshi==2.0.1

cogs/backend/handle/events/_event_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async def silent_snippet_command(ctx: GitBotContext) -> Optional[discord.Message
2727
codeblock: Optional[str] = None
2828
config: AutomaticConversionSettings = await ctx.bot.db.guilds.get_autoconv_config(ctx) # noqa
2929
match_ = None # put the match_ name in the namespace
30-
if (attachment_url := ctx.bot.get_cache_v('carbon', ctx.message.content)) and (config['gh_lines'] == 2 or config.get('codeblock', False)):
30+
if (attachment_url := ctx.bot.get_cache_value('carbon', ctx.message.content)) and (config['gh_lines'] == 2 or config.get('codeblock', False)):
3131
ctx.bot.logger.debug(f'Responding with cached asset URL to MID %d - %s', ctx.message.id, attachment_url)
3232
return await ctx.reply(attachment_url, mention_author=False)
3333
elif (result := ctx.bot.mgr.extract_content_from_codeblock(ctx.message.content)) and config.get('codeblock', False):
@@ -57,7 +57,7 @@ async def silent_snippet_command(ctx: GitBotContext) -> Optional[discord.Message
5757
codeblock, _1st_lineno)),
5858
mention_author=False)
5959
ctx.bot.logger.debug('Carbon asset generation elapsed: %ds', time.time() - start)
60-
ctx.bot.set_cache_v('carbon', ctx.message.content, reply.attachments[0].url)
60+
ctx.bot.set_cache_value('carbon', ctx.message.content, reply.attachments[0].url)
6161
return reply
6262

6363

cogs/backend/handle/events/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async def on_guild_join(self, guild: discord.Guild) -> None:
4646
async def on_guild_remove(self, guild: discord.Guild) -> None:
4747
await self.bot.db.guilds.find_one_and_delete({'_id': guild.id})
4848
try:
49-
self.bot.del_cache_v('autoconv', guild.id)
49+
self.bot.del_cache_value('autoconv', guild.id)
5050
except KeyError:
5151
pass
5252
embed_l: GitBotEmbed = await build_guild_embed(self.bot, guild, False)

cogs/ecosystem/config.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def toggle_autoconv_item(ctx: GitBotContext,
4141
await ctx.bot.db.guilds.update_one({'_id': guild['_id']}, {'$set': {f'autoconv.{item}': state}})
4242
else:
4343
await ctx.bot.db.guilds.insert_one(GitBotGuild(_id=ctx.guild.id, autoconv=config)) # noqa _id is int
44-
ctx.bot.set_cache_v('autoconv', ctx.guild.id, config)
44+
ctx.bot.set_cache_value('autoconv', ctx.guild.id, config)
4545
await ctx.success(ctx.l.config.autoconv.toggles.get(item).get(str(state)))
4646
if not ctx.bot_permissions.read_message_history:
4747
await ctx.hint(ctx.l.generic.hints.read_message_history_permission)
@@ -73,8 +73,8 @@ async def config_show_command_group(self, ctx: GitBotContext) -> None:
7373
lang: str = ctx.fmt('accessibility list locale', f'`{ctx.l.meta.localized_name.capitalize()}`')
7474
user_str, org, repo = (ctx.fmt(f'qa list {item}', self.bot.mgr.to_github_hyperlink(user[item], True) if item in user else
7575
f'`{ctx.l.config.show.base.item_not_set}`') for item in ('user', 'org', 'repo'))
76-
accessibility: list = ctx.l.config.show.base.accessibility.heading + '\n' + '\n'.join([lang])
77-
qa: list = ctx.l.config.show.base.qa.heading + '\n' + '\n'.join([user_str, org, repo])
76+
accessibility: str = ctx.l.config.show.base.accessibility.heading + '\n' + '\n'.join([lang])
77+
qa: str = ctx.l.config.show.base.qa.heading + '\n' + '\n'.join([user_str, org, repo])
7878
guild_str: str = ''
7979
if not isinstance(ctx.channel, discord.DMChannel):
8080
feed: str = ctx.l.config.show.base.guild.list.feed + '\n' + '\n'.join([f'{self.bot.mgr.e.square} <#{rfi["cid"]}>'
@@ -374,7 +374,7 @@ async def config_locale_command(self, ctx: GitBotContext, locale: Optional[str]
374374
return
375375
await self.bot.db.users.setitem(ctx, 'locale', l_[0]['name'])
376376
setattr(ctx, 'l', await self.bot.db.users.get_locale(ctx))
377-
self.bot.set_cache_v('locale', ctx.author.id, l_[0]['name']) # update the cache with the new locale
377+
self.bot.set_cache_value('locale', ctx.author.id, l_[0]['name']) # update the cache with the new locale
378378
await ctx.success_embed(ctx.fmt('success', l_[0]['localized_name'].capitalize()))
379379
return
380380
to_followup = await ctx.error(ctx.fmt('failure', locale))
@@ -472,7 +472,7 @@ async def _callback(_, res: discord.Message):
472472
config: AutomaticConversionSettings = self.bot.mgr.env.autoconv_default
473473
config['gh_lines'] = actual_state
474474
await self.bot.db.guilds.insert_one({'_id': ctx.guild.id, 'autoconv': config})
475-
self.bot.set_cache_v('autoconv', ctx.guild.id, config)
475+
self.bot.set_cache_value('autoconv', ctx.guild.id, config)
476476
await ctx.success(ctx.lp.results[_str])
477477
if not ctx.bot_permissions.read_message_history:
478478
await ctx.hint(ctx.l.generic.hints.read_message_history_permission)

cogs/github/base/repo/repo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async def repo_info_command(self, ctx: GitBotContext, repo: Optional[GitHubRepos
3838
if ctx.data:
3939
r: dict = getattr(ctx, 'data')
4040
else:
41-
r: Optional[dict] = await self.bot.github.get_repo(repo)
41+
r: Optional[dict] = await ctx.gh.get_repo(repo)
4242
if not r:
4343
if ctx.invoked_with_stored:
4444
await self.bot.db.users.delitem(ctx, 'repo')

cogs/github/other/loc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def remove_matches(self, directory: str, pattern: str) -> int:
8585
return c_removed
8686

8787
async def process_repo(self, ctx: GitBotContext, repo: GitHubRepository) -> Optional[tuple[dict, int | None]]:
88-
if (not ctx.__nocache__) and (cached := self.bot.get_cache_v('loc', repo := repo.lower())):
88+
if (not ctx.__nocache__) and (cached := self.bot.get_cache_value('loc', repo := repo.lower())):
8989
return cached
9090
tmp_zip_path: str = f'./tmp/{ctx.message.id}.zip'
9191
tmp_dir_path: str = tmp_zip_path[:-4]
@@ -112,7 +112,7 @@ async def process_repo(self, ctx: GitBotContext, repo: GitHubRepository) -> Opti
112112
except subprocess.CalledProcessError as e:
113113
self.bot.logger.error('the CLOC script failed with exit code %d', e.returncode)
114114
else:
115-
self.bot.set_cache_v('loc', repo, (output, c_removed))
115+
self.bot.set_cache_value('loc', repo, (output, c_removed))
116116
return output, c_removed
117117
finally:
118118
try:

lib/api/github/github.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ async def wrapper(*args: tuple, **kwargs: dict) -> Any:
8787

8888
return wrapper
8989

90+
9091
def _flatten_nodes(func: Callable) -> Callable[..., DictProxy | SnakeCaseDictProxy | None]:
9192
"""
9293
Flattens the nodes field in the response dict by copying the value up one level and removing the old key
@@ -223,10 +224,10 @@ def _sanitize_graphql_variables(variables: dict[str, ...]) -> dict[str, ...]:
223224

224225
async def query(self,
225226
query_or_path: str,
226-
transformer: tuple[LiteralString, ...] | str | Callable[[dict], dict] | None = None,
227+
transformer: tuple[str, ...] | str | Callable[[dict], dict] | None = None,
227228
on_fail_return: _GitHubAPIQueryWrapOnFailReturnDefaultConditionDict |
228229
_GitHubAPIQueryWrapOnFailReturnDefaultNotSet | bool | list | None = 'default_not_set',
229-
**graphql_variables) -> _ReturnDict | list[_ReturnDict] | str | None:
230+
**graphql_variables) -> _ReturnDict | list[_ReturnDict] | str | bool | None:
230231
"""
231232
Wraps a GitHub API query call, handling errors and returning the result.
232233
The request method is chosen between REST and GraphQL based on the query_or_path parameter -
@@ -393,7 +394,8 @@ async def get_repo_zip(self,
393394
@_wrap_proxy
394395
@normalize_repository
395396
async def get_latest_release(self, repo: GitHubRepository) -> Optional[_ReturnDict]:
396-
return await self.query(self.queries.latest_release, _Repo=repo, transformer=transform_latest_release, on_fail_return=None)
397+
return await self.query(self.queries.latest_release, _Repo=repo, transformer=transform_latest_release,
398+
on_fail_return=None)
397399

398400
@_wrap_proxy
399401
@normalize_repository
@@ -471,4 +473,5 @@ async def get_latest_n_releases(self, repo: GitHubRepository, n: int = 5) -> lis
471473
@_flatten_nodes
472474
@normalize_repository
473475
async def get_latest_n_releases_with_repo(self, repo: GitHubRepository, n: int = 5) -> list[_ReturnDict]:
474-
return await self.query(self.queries.latest_releases, _Repo=repo, N=n, on_fail_return=None, transformer=transform_latest_release)
476+
return await self.query(self.queries.latest_releases, _Repo=repo, N=n, on_fail_return=None,
477+
transformer=transform_latest_release)

lib/structs/db/collections/guilds.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async def get_autoconv_config(self,
4242
"""
4343
_did_exist: bool = False
4444

45-
if cached := self.bot.get_cache_v('autoconv', _id):
45+
if cached := self.bot.get_cache_value('autoconv', _id):
4646
_did_exist: bool = True
4747
permission: AutomaticConversionSettings = cached
4848
self.bot.logger.debug('Returning cached auto values for identity "%d"', _id)
@@ -53,5 +53,5 @@ async def get_autoconv_config(self,
5353
_did_exist: bool = True
5454
else:
5555
permission: AutomaticConversionSettings = self.bot.mgr.env.autoconv_default
56-
self.bot.set_cache_v('autoconv', _id, permission)
56+
self.bot.set_cache_value('autoconv', _id, permission)
5757
return permission if not did_exist else (permission, _did_exist)

lib/structs/db/collections/users.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ async def delitem(self, _id: Identity, field: str | Iterable[str]) -> bool:
3939
return False
4040

4141
@normalize_identity()
42-
async def getitem(self, _id: Identity, item: str | Iterable[str]) -> Optional[
42+
async def getitem(self, _id: Identity, item: str | Iterable[str], query_additional_kwargs: Optional[dict] = None) -> Optional[
4343
str | dict | list | bool | int | float]:
44-
query: Optional[dict] = await self.find_one({'_id': _id})
44+
query: Optional[dict] = await self.find_one({'_id': _id, **(query_additional_kwargs or {})})
4545
if query and (ret := get_nested_key(query, item)) is not None:
4646
return ret
4747
return None
@@ -75,14 +75,14 @@ async def get_locale(self, _id: Identity) -> DictProxy:
7575
:return: The locale associated with the user
7676
"""
7777
locale: LocaleName = self.bot.mgr.locale.master.meta.name
78-
if cached := self.bot.get_cache_v('locale', _id):
78+
if cached := self.bot.get_cache_value('locale', _id):
7979
locale: LocaleName = cached
8080
self.bot.logger.debug('Returning cached locale for identity "%d"', _id)
8181
else:
8282
if stored := await self.getitem(_id, 'locale'):
8383
locale: str = stored
8484
try:
85-
self.bot.set_cache_v('locale', _id, locale)
85+
self.bot.set_cache_value('locale', _id, locale)
8686
return getattr(self.bot.mgr.l, locale)
8787
except AttributeError:
8888
return self.bot.mgr.locale.master

lib/structs/discord/bot.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
load_dotenv()
3535

3636
__all__: tuple = ('GitBot',)
37+
_CacheNameT = Literal['autoconv', 'locale', 'carbon', 'loc']
3738

3839

3940
class GitBot(commands.AutoShardedBot):
@@ -56,7 +57,8 @@ class GitBot(commands.AutoShardedBot):
5657
def __init__(self, **kwargs):
5758
self.__init_start: float = perf_counter()
5859
self.user_id_blacklist: set = set()
59-
super().__init__(command_prefix=f'{os.getenv("PREFIX")} ', case_insensitive=True,
60+
super().__init__(command_prefix=commands.when_mentioned_or(f'{os.getenv("PREFIX")} '),
61+
case_insensitive=True,
6062
intents=discord.Intents(messages=True, message_content=True, guilds=True,
6163
guild_reactions=True),
6264
help_command=None, guild_ready_timeout=1,
@@ -202,7 +204,8 @@ async def reload_extension(self, name: str, *, package=None):
202204
await super().reload_extension(name, package=package)
203205
self.logger.info('Reloaded extension: "%s"', name)
204206

205-
def get_cache(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc']) -> TypedCache | SelfHashingCache | None:
207+
def get_cache(self,
208+
cache_name: _CacheNameT) -> TypedCache | SelfHashingCache | None:
206209
"""
207210
Get a cache by name
208211
@@ -212,7 +215,7 @@ def get_cache(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc'])
212215

213216
return self.__caches__.get(cache_name)
214217

215-
def get_cache_v(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc'], key: Any) -> Any:
218+
def get_cache_value(self, cache_name: _CacheNameT, key: Any) -> Any:
216219
"""
217220
Get a cache value by cache name and key
218221
@@ -223,7 +226,7 @@ def get_cache_v(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc']
223226

224227
return self.__caches__.get(cache_name, {}).get(key)
225228

226-
def set_cache_v(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc'], key: Any, value: Any) -> None:
229+
def set_cache_value(self, cache_name: _CacheNameT, key: Any, value: Any) -> None:
227230
"""
228231
Set a cache value
229232
@@ -234,7 +237,7 @@ def set_cache_v(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc']
234237

235238
self.__caches__[cache_name][key] = value
236239

237-
def del_cache_v(self, cache_name: Literal['autoconv', 'locale', 'carbon', 'loc'], key: Any) -> None:
240+
def del_cache_value(self, cache_name: _CacheNameT, key: Any) -> None:
238241
"""
239242
Delete a cache value
240243

lib/structs/discord/context.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import enum
1212
from discord.ext import commands
1313
from typing import Optional, Any, Sequence
14-
from lib.typehints import EmbedLike
14+
from lib.typehints import EmbedLike, LocaleDictProxyDef
1515
from lib.structs import DictProxy
1616
from lib.structs.discord.embed import GitBotEmbed
1717
from lib.structs.discord.commands import GitBotCommand, GitBotCommandGroup, GitBotHybridCommandGroup
@@ -21,7 +21,7 @@
2121
if TYPE_CHECKING:
2222
from aiohttp import ClientSession
2323
from lib.structs import CheckFailureCode, GitBot
24-
from lib.api.github import GitHubQueryDebugInfo
24+
from lib.api.github import GitHubQueryDebugInfo, GitHubAPI
2525

2626
__all__: tuple = ('MessageFormattingStyle', 'GitBotContext')
2727

@@ -38,23 +38,25 @@ class MessageFormattingStyle(enum.Enum):
3838

3939

4040
class GitBotContext(commands.Context):
41+
l: LocaleDictProxyDef | None
4142
bot: 'GitBot'
4243
command: GitBotCommand | GitBotCommandGroup
4344
check_failure_code: Union[int, 'CheckFailureCode'] | None = None
4445
gh_query_debug: Optional['GitHubQueryDebugInfo'] = None
4546
lines_total: int | None = None
47+
gh: 'GitHubAPI'
4648
__nocache__: bool = False
4749
__autoinvoked__: bool = False
4850
__silence_error_calls__: bool = False
4951

5052
def __init__(self, **attrs):
51-
self.command: GitBotCommand | GitBotCommandGroup
5253
super().__init__(**attrs)
5354
self.session: ClientSession = self.bot.session
5455
self.fmt = self.bot.mgr.fmt(self)
5556
self.l = None # noqa
5657
self.data: Optional[dict] = None # field used by chained invocations and quick access
5758
self.invoked_with_stored: bool = False
59+
self.gh: 'GitHubAPI' = self.bot.github # overwritten in bot.py before_invoke hook
5860

5961
def _format_content(self, content: str, style: MessageFormattingStyle | str) -> str:
6062
match MessageFormattingStyle(style):

lib/typehints/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from lib.typehints.db.guild.autoconv import AutomaticConversionSettings
77
from lib.typehints.generic import *
88
from lib.typehints.locale.help import *
9+
from lib.typehints.locale.localedef_wfallback import LocaleDictProxyDef
910
from lib.typehints.db.guild.release_feed import *
1011
from lib.typehints.gitbot_config import *
1112

@@ -35,7 +36,8 @@
3536
'CommandGroupHelp',
3637
'CratesIOCrate',
3738
'ReleaseFeedItemMention',
38-
'GitbotRepoConfig'
39+
'GitbotRepoConfig',
40+
'LocaleDictProxyDef'
3941
)
4042

4143
AnyDict = dict | DictProxy | CaseInsensitiveDict | MaxAgeDict | FixedSizeOrderedDict
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# coding: utf-8
2+
3+
try:
4+
from resources.gen.locale_schema import Locale as LocaleDictProxyDef
5+
except ImportError:
6+
from lib.structs.proxies.dict_proxy import DictProxy
7+
LocaleDictProxyDef = DictProxy
8+
9+
__all__: tuple = ('LocaleDictProxyDef',)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# coding: utf-8
2+
3+
from lib.structs.proxies.dict_proxy import DictProxy
4+
5+
try:
6+
from resources.gen.locale_schema import Locale as LocaleDictProxyDef
7+
except ImportError:
8+
LocaleDictProxyDef = DictProxy
9+
10+
try:
11+
from resources.gen.env_defaults_schema import EnvDefaults as EnvDefaultsProxyDef
12+
except ImportError:
13+
EnvDefaultsProxyDef = DictProxy
14+
15+
16+
__all__: tuple = ('LocaleDictProxyDef', 'EnvDefaultsProxyDef')

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
typeshi==2.0.1

resources/env_defaults.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"github": {
2424
"scopes": [
2525
"user:email",
26-
"read:user"
26+
"read:user",
27+
"repo"
2728
],
2829
"auth_timeout": 150
2930
}

0 commit comments

Comments
 (0)