-
-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathRemoteCardType.py
More file actions
executable file
·114 lines (95 loc) · 4.34 KB
/
Copy pathRemoteCardType.py
File metadata and controls
executable file
·114 lines (95 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import sys
from importlib.util import spec_from_file_location, module_from_spec
from pathlib import Path
from typing import Optional
from requests import get
from tinydb import where
from modules.BaseCardType import BaseCardType
from modules.CleanPath import CleanPath
from modules.Debug import log
from modules.PersistentDatabase import PersistentDatabase
from modules.RemoteFile import RemoteFile
class RemoteCardType:
"""
This class defines a remote CardType. This is an encapsulation of a
CardType class that, rather than being defined locally, queries the
Maker GitHub for Python classes to dynamically inject in the modules
namespace.
"""
"""Base URL for all remote Card Type files to download"""
URL_BASE = (
'https://raw.githubusercontent.com/CollinHeist/'
'TitleCardMaker-CardTypes/master'
)
"""Temporary directory all card types are written to"""
TEMP_DIR = Path(__file__).parent / '.objects'
"""Database of assets that have been loaded already this run"""
LOADED = 'remote_assets.json'
__slots__ = ('loaded', 'card_class', 'valid')
def __init__(self, remote: str) -> None:
"""
Construct a new RemoteCardType. This downloads the source file
at the specified location and loads it as a class in the global
modules, under the interpreted class name. If the given remote
specification is a file that exists, that file is loaded.
Args:
database_directory: Base Path to read/write any databases
from.
remote: URL to remote card to inject. Should omit repo base.
Should be specified like {username}/{class_name}. Can
also be a local filepath.
"""
# Get database of loaded assets/cards
self.card_class: Optional[BaseCardType] = None
self.loaded = PersistentDatabase(self.LOADED)
self.valid = True
# If local file has been specified..
if (file := CleanPath(remote).sanitize()).exists():
# Get class name from file
class_name = file.stem
file_name = str(file.resolve())
else:
# Get username and class name from the remote specification
username = remote.split('/', maxsplit=1)[0]
class_name = remote.split('/')[-1]
# Download and write the CardType class into a temporary file
file_name = self.TEMP_DIR / f'{username}-{class_name}.py'
url = f'{self.URL_BASE}/{remote}.py'
# Only request and write file if not loaded this run
if (not self.loaded.get(where('remote') == url)
or not file_name.exists()):
# Make GET request for the contents of the specified value
if (response := get(url, timeout=30)).status_code >= 400:
log.error(f'Cannot identify remote Card Type "{remote}"')
self.valid = False
return None
# Write remote file contents to temporary class
file_name.parent.mkdir(parents=True, exist_ok=True)
with (file_name).open('wb') as fh:
fh.write(response.content)
# Import new file as module
try:
# Create module for newly loaded file
spec = spec_from_file_location(class_name, file_name)
module = module_from_spec(spec)
sys.modules[class_name] = module
spec.loader.exec_module(module)
# Get class from module namespace
self.card_class = module.__dict__[class_name]
# Validate that each RemoteFile of this class loaded correctly
for attribute_name in dir(self.card_class):
attribute = getattr(self.card_class, attribute_name)
if isinstance(attribute, RemoteFile):
self.valid &= attribute.valid
# Add this url to the loaded database
try:
self.loaded.insert({'remote': url})
log.debug(f'Loaded RemoteCardType "{remote}"')
except Exception:
pass
except Exception as e:
# Some error in loading, set object as invalid
log.error(f'Cannot load CardType "{remote}", returned "{e}"')
self.card_class = None
self.valid = False
return None