levistone 0.10.15__cp312-cp312-win_amd64.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.
@@ -0,0 +1,101 @@
1
+ import platform
2
+ from pathlib import Path
3
+ from typing import Any, Dict
4
+
5
+ import psutil
6
+ from endstone_bstats import AdvancedPie, DrilldownPie, MetricsBase, MetricsConfig, SimplePie, SingleLineChart
7
+
8
+ from endstone import Server, __minecraft_version__
9
+
10
+
11
+ class Metrics(MetricsBase):
12
+ def __init__(self, server: Server):
13
+ self._server = server
14
+
15
+ # Get the config file
16
+ bstats_folder = Path("plugins") / "bstats"
17
+ config_file = bstats_folder / "config.toml"
18
+ self._config = MetricsConfig(config_file, True)
19
+
20
+ super().__init__(
21
+ platform="server-implementation",
22
+ server_uuid=self._config.server_uuid,
23
+ service_id=23296,
24
+ log_errors=self._config.log_errors_enabled,
25
+ log_sent_data=self._config.log_sent_data_enabled,
26
+ log_response_status_text=self._config.log_response_status_text_enabled,
27
+ )
28
+
29
+ self.add_custom_chart(SingleLineChart("players", lambda: len(self._server.online_players)))
30
+ self.add_custom_chart(SimplePie("endstone_version", lambda: self._server.version))
31
+ self.add_custom_chart(SimplePie("minecraft_version", lambda: __minecraft_version__))
32
+ self.add_custom_chart(DrilldownPie("online_mode", self._get_online_mode))
33
+ self.add_custom_chart(DrilldownPie("python_version", self._get_python_version))
34
+ self.add_custom_chart(AdvancedPie("player_platform", self._get_player_platforms))
35
+ self.add_custom_chart(AdvancedPie("player_game_version", self._get_player_game_versions))
36
+
37
+ @property
38
+ def enabled(self) -> bool:
39
+ return self._config.enabled
40
+
41
+ @property
42
+ def service_enabled(self) -> bool:
43
+ return True
44
+
45
+ def append_platform_data(self, platform_data: Dict[str, Any]) -> None:
46
+ os_name = platform.system()
47
+ if os_name == "Windows":
48
+ platform_data["osName"] = f"Windows {platform.release()}"
49
+ platform_data["osVersion"] = platform.version()
50
+ elif os_name == "Linux":
51
+ platform_data["osName"] = "Linux"
52
+ platform_data["osVersion"] = platform.release()
53
+
54
+ os_arch = platform.machine().lower()
55
+ if os_arch == "x86_64":
56
+ os_arch = "amd64"
57
+ platform_data["osArch"] = os_arch
58
+ platform_data["coreCount"] = psutil.cpu_count(logical=False)
59
+
60
+ def log_info(self, message: str) -> None:
61
+ self._server.logger.info(message)
62
+
63
+ def log_error(self, message: str, exception: Exception) -> None:
64
+ self._server.logger.warning(f"{message}: {exception}")
65
+
66
+ def _get_online_mode(self) -> dict[str, dict[str, int]]:
67
+ value = "true" if self._server.online_mode else "false"
68
+ return {
69
+ value: {
70
+ value: 1,
71
+ },
72
+ }
73
+
74
+ def _get_python_version(self) -> dict[str, dict[str, int]]:
75
+ python_impl = platform.python_implementation()
76
+ major, minor, patch = platform.python_version_tuple()
77
+ return {
78
+ f"{python_impl} {major}.{minor}": {
79
+ f"{major}.{minor}.{patch}": 1,
80
+ },
81
+ }
82
+
83
+ def _get_player_platforms(self) -> dict[str, int]:
84
+ result: dict[str, int] = {}
85
+ for player in self._server.online_players:
86
+ if player.device_os not in result:
87
+ result[player.device_os] = 1
88
+ else:
89
+ result[player.device_os] += 1
90
+
91
+ return result
92
+
93
+ def _get_player_game_versions(self) -> dict[str, int]:
94
+ result: dict[str, int] = {}
95
+ for player in self._server.online_players:
96
+ if player.game_version not in result:
97
+ result[player.game_version] = 1
98
+ else:
99
+ result[player.game_version] += 1
100
+
101
+ return result
@@ -0,0 +1,252 @@
1
+ from __future__ import annotations
2
+
3
+ import glob
4
+ import importlib
5
+ import os
6
+ import os.path
7
+ import shutil
8
+ import site
9
+ import subprocess
10
+ import sys
11
+ import traceback
12
+ import warnings
13
+
14
+ import pkginfo
15
+ from importlib_metadata import EntryPoint, distribution, entry_points, metadata
16
+
17
+ from endstone import Server
18
+ from endstone._internal.metrics import Metrics
19
+ from endstone.command import Command
20
+ from endstone.permissions import Permission, PermissionDefault
21
+ from endstone.plugin import Plugin, PluginDescription, PluginLoader, PluginLoadOrder
22
+
23
+ __all__ = ["PythonPluginLoader"]
24
+
25
+ warnings.simplefilter(action="always", category=FutureWarning)
26
+
27
+
28
+ def find_python():
29
+ paths = []
30
+ if os.environ.get("ENDSTONE_PYTHON_EXECUTABLE", None) is not None:
31
+ paths.append(os.environ["ENDSTONE_PYTHON_EXECUTABLE"])
32
+
33
+ if sys.platform == "win32":
34
+ paths.append(os.path.join(sys.base_prefix, "python.exe"))
35
+ else:
36
+ paths.append(
37
+ os.path.join(sys.base_prefix, "bin", "python" + f"{sys.version_info.major}.{sys.version_info.minor}")
38
+ )
39
+ paths.append(os.path.join(sys.base_prefix, "bin", "python" + f"{sys.version_info.major}"))
40
+ paths.append(os.path.join(sys.base_prefix, "bin", "python"))
41
+
42
+ paths = set(paths)
43
+ for path in paths:
44
+ if os.path.isfile(path):
45
+ return path
46
+
47
+ raise RuntimeError(f"Unable to find Python executable. Attempted paths: {paths}")
48
+
49
+
50
+ def _build_commands(commands: dict) -> list[Command]:
51
+ results = []
52
+ for name, command in commands.items():
53
+ command = Command(name, **command)
54
+ results.append(command)
55
+ return results
56
+
57
+
58
+ def _build_permissions(permissions: dict) -> list[Permission]:
59
+ results = []
60
+ for name, permission in permissions.items():
61
+ if "default" in permission:
62
+ value = permission["default"]
63
+ if isinstance(value, bool):
64
+ permission["default"] = PermissionDefault.TRUE if value else PermissionDefault.FALSE
65
+ elif isinstance(value, str):
66
+ permission["default"] = PermissionDefault.__members__[value.strip().replace(" ", "_").upper()]
67
+ elif not isinstance(value, PermissionDefault):
68
+ raise TypeError(f"Invalid value for default permission: {value}")
69
+
70
+ permission = Permission(name, **permission)
71
+ results.append(permission)
72
+ return results
73
+
74
+
75
+ class PythonPluginLoader(PluginLoader):
76
+ SUPPORTED_API = ["0.5", "0.6", "0.7", "0.8", "0.9", "0.10"]
77
+
78
+ def __init__(self, server: Server):
79
+ PluginLoader.__init__(self, server)
80
+
81
+ self._plugins = []
82
+
83
+ # fix sys.executable variable
84
+ sys.executable = find_python()
85
+ sys._base_executable = sys.executable
86
+
87
+ # invalidate previously loaded modules (in case of /reload)
88
+ importlib.invalidate_caches()
89
+ for module in list(sys.modules.keys()):
90
+ if module.startswith("endstone_"):
91
+ del sys.modules[module]
92
+
93
+ # prepare the temp site-dir
94
+ self._prefix = os.path.join("plugins", ".local")
95
+ for site_dir in site.getsitepackages(prefixes=[self._prefix]):
96
+ site.addsitedir(site_dir)
97
+
98
+ # delete old and/or invalid packages
99
+ if (
100
+ os.path.exists(site_dir)
101
+ and os.path.commonpath([site_dir, self._prefix]) == self._prefix
102
+ and site_dir != self._prefix
103
+ ):
104
+ for directory in os.listdir(site_dir):
105
+ if not os.path.isdir(os.path.join(site_dir, directory)):
106
+ continue
107
+ if directory.startswith("endstone_") or directory.startswith("~"):
108
+ shutil.rmtree(os.path.join(site_dir, directory))
109
+
110
+ # initialize the metrics
111
+ self._metrics = Metrics(self.server)
112
+
113
+ def __del__(self):
114
+ self._metrics.shutdown()
115
+
116
+ def load_plugin(self, file: str) -> Plugin | None:
117
+ env = os.environ.copy()
118
+ env.pop("LD_PRELOAD", "")
119
+
120
+ dist_name = pkginfo.Wheel(file).name
121
+ subprocess.run(
122
+ [
123
+ sys.executable,
124
+ "-m",
125
+ "pip",
126
+ "install",
127
+ file,
128
+ "--prefix",
129
+ self._prefix,
130
+ "--quiet",
131
+ "--no-warn-script-location",
132
+ "--disable-pip-version-check",
133
+ ],
134
+ env=env,
135
+ )
136
+
137
+ eps = distribution(dist_name).entry_points.select(group="endstone")
138
+ for ep in eps:
139
+ plugin = self._load_plugin_from_ep(ep)
140
+ if plugin:
141
+ return plugin
142
+
143
+ return None
144
+
145
+ def load_plugins(self, directory: str) -> list[Plugin]:
146
+ loaded_plugins = []
147
+
148
+ if not self._plugins:
149
+ eps = entry_points(group="endstone")
150
+ for ep in eps:
151
+ plugin = self._load_plugin_from_ep(ep)
152
+ if plugin:
153
+ loaded_plugins.append(plugin)
154
+
155
+ for file in glob.glob(os.path.join(directory, "*.whl")):
156
+ plugin = self.load_plugin(file)
157
+ if plugin:
158
+ loaded_plugins.append(plugin)
159
+
160
+ return loaded_plugins
161
+
162
+ def _load_plugin_from_ep(self, ep: EntryPoint) -> Plugin | None:
163
+ # enforce naming convention
164
+ if not ep.dist.name.replace("_", "-").startswith("endstone-"):
165
+ self.server.logger.error(
166
+ f"Error occurred when trying to load plugin from entry point '{ep.name}': Invalid name."
167
+ )
168
+ self.server.logger.error(
169
+ f"The name of distribution ({ep.dist.name}) does not start with 'endstone-' or 'endstone_'."
170
+ )
171
+ return None
172
+
173
+ dist_name = "endstone-" + ep.name.replace("_", "-")
174
+ if ep.dist.name.replace("_", "-") != dist_name:
175
+ self.server.logger.error(
176
+ f"Error occurred when trying to load plugin from entry point '{ep.name}': Invalid name."
177
+ )
178
+ self.server.logger.error("You need to make **ONE** of the following changes.")
179
+ self.server.logger.error(
180
+ f"* If you intend to use the current entry point name ({ep.name}), "
181
+ f"please change the distribution name from '{ep.dist.name}' to '{dist_name}'."
182
+ )
183
+ self.server.logger.error(
184
+ f"* If not, please change the entry point name from '{ep.name}' to '{ep.dist.name[9:]}'."
185
+ )
186
+ return None
187
+
188
+ # get distribution metadata
189
+ try:
190
+ plugin_metadata = metadata(ep.dist.name).json
191
+ cls = ep.load()
192
+ except BaseException as e:
193
+ self.server.logger.error(f"Error occurred when trying to load plugin from entry point '{ep.name}':")
194
+ self.server.logger.error("".join(traceback.format_exception(e)))
195
+ return None
196
+
197
+ # prepare plugin description
198
+ cls_attr = dict(cls.__dict__)
199
+ name = cls_attr.pop("name", ep.name.replace("-", "_"))
200
+ version = cls_attr.pop("version", plugin_metadata["version"])
201
+
202
+ api_version = cls_attr.pop("api_version", None)
203
+ if api_version is None:
204
+ self.server.logger.warning(
205
+ f"Plugin '{name}' does not specify an API version. This may prevent the plugin from loading in "
206
+ f"future releases."
207
+ )
208
+ elif api_version not in self.SUPPORTED_API:
209
+ self.server.logger.error(
210
+ f"Error occurred when trying to load plugin '{name}': plugin was designed for API version: "
211
+ f"{api_version} which is not compatible with this server."
212
+ )
213
+ return None
214
+
215
+ load = cls_attr.pop("load", None)
216
+ if load is not None:
217
+ if isinstance(load, str):
218
+ load = PluginLoadOrder.__members__[load.strip().replace(" ", "_").upper()]
219
+ elif not isinstance(load, PluginLoadOrder):
220
+ raise TypeError(f"Invalid value for load order: {load}")
221
+
222
+ description = cls_attr.pop("description", plugin_metadata.get("summary", None))
223
+ authors = cls_attr.pop("authors", plugin_metadata.get("author_email", "").split(","))
224
+ website = cls_attr.pop("website", "; ".join(plugin_metadata.get("project_url", [])))
225
+
226
+ commands = cls_attr.pop("commands", {})
227
+ commands = _build_commands(commands)
228
+
229
+ permissions = cls_attr.pop("permissions", {})
230
+ permissions = _build_permissions(permissions)
231
+
232
+ plugin_description = PluginDescription(
233
+ name=name,
234
+ version=version,
235
+ load=load,
236
+ description=description,
237
+ authors=authors,
238
+ website=website,
239
+ commands=commands,
240
+ permissions=permissions,
241
+ **cls_attr,
242
+ )
243
+
244
+ # instantiate plugin
245
+ plugin = cls()
246
+ if not isinstance(plugin, Plugin):
247
+ self.server.logger.error(f"Main class {ep.value} does not extend endstone.plugin.Plugin")
248
+ return None
249
+
250
+ plugin._description = plugin_description
251
+ self._plugins.append(plugin)
252
+ return plugin
@@ -0,0 +1,14 @@
1
+ TYPE_CHECKING = False
2
+ if TYPE_CHECKING:
3
+ from typing import Tuple, Union
4
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
5
+ else:
6
+ VERSION_TUPLE = object
7
+
8
+ version: str
9
+ __version__: str
10
+ __version_tuple__: VERSION_TUPLE
11
+ version_tuple: VERSION_TUPLE
12
+
13
+ __version__ = version = '0.10.15'
14
+ __version_tuple__ = version_tuple = (0, 10, 15, '')
endstone/actor.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Actor, Item, Mob
2
+
3
+ __all__ = ["Actor", "Item", "Mob"]
endstone/ban.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import IpBanEntry, IpBanList, PlayerBanEntry, PlayerBanList
2
+
3
+ __all__ = ["IpBanEntry", "IpBanList", "PlayerBanEntry", "PlayerBanList"]
endstone/block.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Block, BlockData, BlockFace, BlockState
2
+
3
+ __all__ = ["Block", "BlockData", "BlockFace", "BlockState"]
endstone/boss.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import BarColor, BarFlag, BarStyle, BossBar
2
+
3
+ __all__ = ["BossBar", "BarColor", "BarFlag", "BarStyle"]
endstone/command.py ADDED
@@ -0,0 +1,17 @@
1
+ from endstone._internal.endstone_python import (
2
+ BlockCommandSender,
3
+ Command,
4
+ CommandExecutor,
5
+ CommandSender,
6
+ CommandSenderWrapper,
7
+ ConsoleCommandSender,
8
+ )
9
+
10
+ __all__ = [
11
+ "BlockCommandSender",
12
+ "Command",
13
+ "CommandExecutor",
14
+ "CommandSender",
15
+ "CommandSenderWrapper",
16
+ "ConsoleCommandSender",
17
+ ]
@@ -0,0 +1,9 @@
1
+ [commands]
2
+ # Whether to log commands executed by players, in chat or on signs.
3
+ # This currently logs in the format "<playername> issued server command: <command>".
4
+ log = true
5
+
6
+ [settings]
7
+ # Allow clients to use their own packs when texturepack-required is true.
8
+ # Has no effect if texturepack-required is false.
9
+ allow-client-packs = false
endstone/damage.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import DamageSource
2
+
3
+ __all__ = ["DamageSource"]
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Enchantment
2
+
3
+ __all__ = ["Enchantment"]
endstone/event.py ADDED
@@ -0,0 +1,143 @@
1
+ from endstone._internal.endstone_python import (
2
+ ActorDamageEvent,
3
+ ActorDeathEvent,
4
+ ActorEvent,
5
+ ActorExplodeEvent,
6
+ ActorKnockbackEvent,
7
+ ActorRemoveEvent,
8
+ ActorSpawnEvent,
9
+ ActorTeleportEvent,
10
+ BlockBreakEvent,
11
+ BlockCookEvent,
12
+ BlockEvent,
13
+ BlockPistonEvent,
14
+ BlockPistonExtendEvent,
15
+ BlockPistonRetractEvent,
16
+ BlockPlaceEvent,
17
+ BroadcastMessageEvent,
18
+ Cancellable,
19
+ ChunkEvent,
20
+ ChunkLoadEvent,
21
+ ChunkUnloadEvent,
22
+ DimensionEvent,
23
+ Event,
24
+ EventPriority,
25
+ EventResult,
26
+ LeavesDecayEvent,
27
+ LevelEvent,
28
+ MobEvent,
29
+ PacketReceiveEvent,
30
+ PacketSendEvent,
31
+ PlayerBedEnterEvent,
32
+ PlayerBedLeaveEvent,
33
+ PlayerChatEvent,
34
+ PlayerCommandEvent,
35
+ PlayerDeathEvent,
36
+ PlayerDropItemEvent,
37
+ PlayerEmoteEvent,
38
+ PlayerEvent,
39
+ PlayerGameModeChangeEvent,
40
+ PlayerInteractActorEvent,
41
+ PlayerInteractEvent,
42
+ PlayerItemConsumeEvent,
43
+ PlayerItemHeldEvent,
44
+ PlayerJoinEvent,
45
+ PlayerJumpEvent,
46
+ PlayerKickEvent,
47
+ PlayerLoginEvent,
48
+ PlayerMoveEvent,
49
+ PlayerPickupItemEvent,
50
+ PlayerQuitEvent,
51
+ PlayerRespawnEvent,
52
+ PlayerSkinChangeEvent,
53
+ PlayerTeleportEvent,
54
+ PluginDisableEvent,
55
+ PluginEnableEvent,
56
+ ScriptMessageEvent,
57
+ ServerCommandEvent,
58
+ ServerEvent,
59
+ ServerListPingEvent,
60
+ ServerLoadEvent,
61
+ ThunderChangeEvent,
62
+ WeatherChangeEvent,
63
+ WeatherEvent,
64
+ )
65
+
66
+ __all__ = [
67
+ "event_handler",
68
+ "ActorEvent",
69
+ "ActorDamageEvent",
70
+ "ActorDeathEvent",
71
+ "ActorExplodeEvent",
72
+ "ActorKnockbackEvent",
73
+ "ActorRemoveEvent",
74
+ "ActorSpawnEvent",
75
+ "ActorTeleportEvent",
76
+ "BlockEvent",
77
+ "BlockBreakEvent",
78
+ "BlockCookEvent",
79
+ "BlockPistonEvent",
80
+ "BlockPistonExtendEvent",
81
+ "BlockPistonRetractEvent",
82
+ "BlockPlaceEvent",
83
+ "Cancellable",
84
+ "ChunkEvent",
85
+ "ChunkLoadEvent",
86
+ "ChunkUnloadEvent",
87
+ "DimensionEvent",
88
+ "Event",
89
+ "EventPriority",
90
+ "EventResult",
91
+ "LeavesDecayEvent",
92
+ "LevelEvent",
93
+ "MobEvent",
94
+ "PacketReceiveEvent",
95
+ "PacketSendEvent",
96
+ "PlayerEvent",
97
+ "PlayerBedEnterEvent",
98
+ "PlayerBedLeaveEvent",
99
+ "PlayerChatEvent",
100
+ "PlayerCommandEvent",
101
+ "PlayerDeathEvent",
102
+ "PlayerDropItemEvent",
103
+ "PlayerEmoteEvent",
104
+ "PlayerGameModeChangeEvent",
105
+ "PlayerInteractEvent",
106
+ "PlayerInteractActorEvent",
107
+ "PlayerItemConsumeEvent",
108
+ "PlayerItemHeldEvent",
109
+ "PlayerJoinEvent",
110
+ "PlayerJumpEvent",
111
+ "PlayerKickEvent",
112
+ "PlayerLoginEvent",
113
+ "PlayerMoveEvent",
114
+ "PlayerPickupItemEvent",
115
+ "PlayerQuitEvent",
116
+ "PlayerRespawnEvent",
117
+ "PlayerSkinChangeEvent",
118
+ "PlayerTeleportEvent",
119
+ "BroadcastMessageEvent",
120
+ "PluginEnableEvent",
121
+ "PluginDisableEvent",
122
+ "ScriptMessageEvent",
123
+ "ServerEvent",
124
+ "ServerCommandEvent",
125
+ "ServerListPingEvent",
126
+ "ServerLoadEvent",
127
+ "ThunderChangeEvent",
128
+ "WeatherEvent",
129
+ "WeatherChangeEvent",
130
+ ]
131
+
132
+
133
+ def event_handler(func=None, *, priority: EventPriority = EventPriority.NORMAL, ignore_cancelled: bool = False):
134
+ def decorator(f):
135
+ setattr(f, "_is_event_handler", True)
136
+ setattr(f, "_priority", priority)
137
+ setattr(f, "_ignore_cancelled", ignore_cancelled)
138
+ return f
139
+
140
+ if func:
141
+ return decorator(func)
142
+
143
+ return decorator
endstone/form.py ADDED
@@ -0,0 +1,29 @@
1
+ from endstone._internal.endstone_python import (
2
+ ActionForm,
3
+ Button,
4
+ Divider,
5
+ Dropdown,
6
+ Header,
7
+ Label,
8
+ MessageForm,
9
+ ModalForm,
10
+ Slider,
11
+ StepSlider,
12
+ TextInput,
13
+ Toggle,
14
+ )
15
+
16
+ __all__ = [
17
+ "ActionForm",
18
+ "Button",
19
+ "MessageForm",
20
+ "ModalForm",
21
+ "Dropdown",
22
+ "Label",
23
+ "Slider",
24
+ "StepSlider",
25
+ "TextInput",
26
+ "Toggle",
27
+ "Divider",
28
+ "Header",
29
+ ]
endstone/inventory.py ADDED
@@ -0,0 +1,21 @@
1
+ from endstone._internal.endstone_python import (
2
+ EquipmentSlot,
3
+ Inventory,
4
+ ItemFactory,
5
+ ItemMeta,
6
+ ItemStack,
7
+ ItemType,
8
+ MapMeta,
9
+ PlayerInventory,
10
+ )
11
+
12
+ __all__ = [
13
+ "EquipmentSlot",
14
+ "Inventory",
15
+ "ItemFactory",
16
+ "ItemMeta",
17
+ "ItemStack",
18
+ "ItemType",
19
+ "MapMeta",
20
+ "PlayerInventory",
21
+ ]
endstone/lang.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Language, Translatable
2
+
3
+ __all__ = ["Language", "Translatable"]
endstone/level.py ADDED
@@ -0,0 +1,9 @@
1
+ from endstone._internal.endstone_python import Chunk, Dimension, Level, Location, Position
2
+
3
+ __all__ = [
4
+ "Chunk",
5
+ "Dimension",
6
+ "Level",
7
+ "Location",
8
+ "Position",
9
+ ]
endstone/map.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import MapCanvas, MapRenderer, MapView
2
+
3
+ __all__ = ["MapCanvas", "MapRenderer", "MapView"]
@@ -0,0 +1,17 @@
1
+ from endstone._internal.endstone_python import (
2
+ Permissible,
3
+ Permission,
4
+ PermissionAttachment,
5
+ PermissionAttachmentInfo,
6
+ PermissionDefault,
7
+ PermissionLevel,
8
+ )
9
+
10
+ __all__ = [
11
+ "Permission",
12
+ "Permissible",
13
+ "PermissionAttachment",
14
+ "PermissionAttachmentInfo",
15
+ "PermissionDefault",
16
+ "PermissionLevel",
17
+ ]