pycascadeui 1.0.0__tar.gz

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.
Files changed (69) hide show
  1. pycascadeui-1.0.0/LICENSE +21 -0
  2. pycascadeui-1.0.0/PKG-INFO +370 -0
  3. pycascadeui-1.0.0/README.md +328 -0
  4. pycascadeui-1.0.0/cascadeui/__init__.py +128 -0
  5. pycascadeui-1.0.0/cascadeui/components/__init__.py +62 -0
  6. pycascadeui-1.0.0/cascadeui/components/base.py +89 -0
  7. pycascadeui-1.0.0/cascadeui/components/buttons.py +110 -0
  8. pycascadeui-1.0.0/cascadeui/components/composition.py +56 -0
  9. pycascadeui-1.0.0/cascadeui/components/inputs.py +153 -0
  10. pycascadeui-1.0.0/cascadeui/components/patterns.py +250 -0
  11. pycascadeui-1.0.0/cascadeui/components/selects.py +101 -0
  12. pycascadeui-1.0.0/cascadeui/components/wrappers.py +210 -0
  13. pycascadeui-1.0.0/cascadeui/devtools.py +323 -0
  14. pycascadeui-1.0.0/cascadeui/persistence/__init__.py +33 -0
  15. pycascadeui-1.0.0/cascadeui/persistence/migration.py +49 -0
  16. pycascadeui-1.0.0/cascadeui/persistence/redis.py +101 -0
  17. pycascadeui-1.0.0/cascadeui/persistence/serialization.py +35 -0
  18. pycascadeui-1.0.0/cascadeui/persistence/sqlite.py +117 -0
  19. pycascadeui-1.0.0/cascadeui/persistence/storage.py +133 -0
  20. pycascadeui-1.0.0/cascadeui/py.typed +0 -0
  21. pycascadeui-1.0.0/cascadeui/state/__init__.py +20 -0
  22. pycascadeui-1.0.0/cascadeui/state/actions.py +140 -0
  23. pycascadeui-1.0.0/cascadeui/state/computed.py +65 -0
  24. pycascadeui-1.0.0/cascadeui/state/middleware.py +121 -0
  25. pycascadeui-1.0.0/cascadeui/state/reducers.py +425 -0
  26. pycascadeui-1.0.0/cascadeui/state/singleton.py +23 -0
  27. pycascadeui-1.0.0/cascadeui/state/store.py +640 -0
  28. pycascadeui-1.0.0/cascadeui/state/types.py +37 -0
  29. pycascadeui-1.0.0/cascadeui/state/undo.py +109 -0
  30. pycascadeui-1.0.0/cascadeui/theming/__init__.py +27 -0
  31. pycascadeui-1.0.0/cascadeui/theming/core.py +99 -0
  32. pycascadeui-1.0.0/cascadeui/theming/themes.py +57 -0
  33. pycascadeui-1.0.0/cascadeui/utils/__init__.py +20 -0
  34. pycascadeui-1.0.0/cascadeui/utils/decorators.py +60 -0
  35. pycascadeui-1.0.0/cascadeui/utils/errors.py +154 -0
  36. pycascadeui-1.0.0/cascadeui/utils/logging.py +534 -0
  37. pycascadeui-1.0.0/cascadeui/utils/tasks.py +82 -0
  38. pycascadeui-1.0.0/cascadeui/validation.py +145 -0
  39. pycascadeui-1.0.0/cascadeui/views/__init__.py +20 -0
  40. pycascadeui-1.0.0/cascadeui/views/base.py +905 -0
  41. pycascadeui-1.0.0/cascadeui/views/patterns.py +238 -0
  42. pycascadeui-1.0.0/cascadeui/views/persistent.py +351 -0
  43. pycascadeui-1.0.0/cascadeui/views/specialized.py +525 -0
  44. pycascadeui-1.0.0/pycascadeui.egg-info/PKG-INFO +370 -0
  45. pycascadeui-1.0.0/pycascadeui.egg-info/SOURCES.txt +67 -0
  46. pycascadeui-1.0.0/pycascadeui.egg-info/dependency_links.txt +1 -0
  47. pycascadeui-1.0.0/pycascadeui.egg-info/requires.txt +18 -0
  48. pycascadeui-1.0.0/pycascadeui.egg-info/top_level.txt +1 -0
  49. pycascadeui-1.0.0/pyproject.toml +76 -0
  50. pycascadeui-1.0.0/setup.cfg +4 -0
  51. pycascadeui-1.0.0/tests/test_auto_defer.py +206 -0
  52. pycascadeui-1.0.0/tests/test_backends.py +122 -0
  53. pycascadeui-1.0.0/tests/test_batching.py +154 -0
  54. pycascadeui-1.0.0/tests/test_components.py +50 -0
  55. pycascadeui-1.0.0/tests/test_computed.py +118 -0
  56. pycascadeui-1.0.0/tests/test_hooks.py +105 -0
  57. pycascadeui-1.0.0/tests/test_middleware.py +129 -0
  58. pycascadeui-1.0.0/tests/test_nav_stack.py +285 -0
  59. pycascadeui-1.0.0/tests/test_owner_only.py +146 -0
  60. pycascadeui-1.0.0/tests/test_pagination.py +435 -0
  61. pycascadeui-1.0.0/tests/test_persistent_views.py +178 -0
  62. pycascadeui-1.0.0/tests/test_reducers.py +147 -0
  63. pycascadeui-1.0.0/tests/test_scoping.py +79 -0
  64. pycascadeui-1.0.0/tests/test_session_limit.py +439 -0
  65. pycascadeui-1.0.0/tests/test_state_store.py +261 -0
  66. pycascadeui-1.0.0/tests/test_theming.py +64 -0
  67. pycascadeui-1.0.0/tests/test_undo.py +212 -0
  68. pycascadeui-1.0.0/tests/test_validation.py +154 -0
  69. pycascadeui-1.0.0/tests/test_view_init.py +168 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2025 HollowTheSilver
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,370 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycascadeui
3
+ Version: 1.0.0
4
+ Summary: Redux-inspired UI framework for discord.py
5
+ Author-email: HollowTheSilver <hollow@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/HollowTheSilver/CascadeUI
8
+ Project-URL: Repository, https://github.com/HollowTheSilver/CascadeUI
9
+ Project-URL: Issues, https://github.com/HollowTheSilver/CascadeUI/issues
10
+ Project-URL: Documentation, https://hollowthesilver.github.io/CascadeUI/
11
+ Keywords: discord,discord.py,ui,components,state-management
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Communications :: Chat
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Classifier: Framework :: AsyncIO
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: discord.py>=2.7
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
31
+ Requires-Dist: black>=23.0; extra == "dev"
32
+ Requires-Dist: isort>=5.12; extra == "dev"
33
+ Provides-Extra: docs
34
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
35
+ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
36
+ Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
37
+ Provides-Extra: sqlite
38
+ Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
39
+ Provides-Extra: redis
40
+ Requires-Dist: redis>=5.0; extra == "redis"
41
+ Dynamic: license-file
42
+
43
+ <p align="center">
44
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/docs/assets/banner.png" alt="CascadeUI — A Redux-Inspired Framework for Discord.py" width="100%">
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://pypi.org/project/pycascadeui/"><img src="https://img.shields.io/pypi/v/pycascadeui?logo=pypi&logoColor=white" alt="PyPI"></a>
49
+ <a href="https://pypi.org/project/pycascadeui/"><img src="https://img.shields.io/pypi/dm/pycascadeui?logo=pypi&logoColor=white&label=downloads" alt="Downloads"></a>
50
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg?logo=python&logoColor=white" alt="Python 3.10-3.14"></a>
51
+ <a href="https://github.com/Rapptz/discord.py"><img src="https://img.shields.io/badge/discord.py-2.7+-738adb.svg?logo=discord&logoColor=white" alt="discord.py 2.7+"></a>
52
+ <a href="https://github.com/HollowTheSilver/CascadeUI/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/HollowTheSilver/CascadeUI/ci.yml?logo=github&label=CI" alt="CI"></a>
53
+ <a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code style: black"></a>
54
+ <a href="https://hollowthesilver.github.io/CascadeUI/"><img src="https://img.shields.io/badge/docs-GitHub%20Pages-8A2BE2?logo=readthedocs" alt="Docs"></a>
55
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
56
+ </p>
57
+
58
+ <p align="center">
59
+ Redux-inspired UI framework for <a href="https://github.com/Rapptz/discord.py">discord.py</a>.<br>
60
+ Centralized state, dispatched actions, reducers, and subscriber notifications<br>
61
+ for predictable state flow and composable UI patterns.
62
+ </p>
63
+
64
+ > [!NOTE]
65
+ > This initial release targets **Discord V1 Components** (Views, Buttons, Selects, Modals). Full V2 Component support (LayoutView, Container, Section, TextDisplay, etc.) is planned for the next major release.
66
+
67
+ <p align="center">
68
+ <a href="https://hollowthesilver.github.io/CascadeUI/"><strong>Read the full documentation</strong></a>
69
+ </p>
70
+
71
+ ![CascadeUI Demo](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/hero-demo.gif)
72
+
73
+ ---
74
+
75
+ ## Features
76
+
77
+ #### State Management
78
+ - **Centralized State Store** -- Singleton store with dispatch/reducer cycle, action batching, computed values, event hooks, and subscriber filtering by action type + state selectors
79
+ - **Custom Reducers** -- Register your own action types with `@cascade_reducer`
80
+ - **Middleware** -- Intercept and transform actions; built-in debounced persistence, logging, and undo/redo
81
+
82
+ #### Views & Patterns
83
+ - **Stateful Views** -- Wraps discord.py `View` with lifecycle management, auto-defer safety net, interaction ownership, navigation stack (push/pop), state scoping, and undo/redo
84
+ - **Session Limiting** -- Declarative per-view limits (`session_limit`, `session_scope`, `session_policy`) with automatic old-view cleanup
85
+ - **Pre-built Patterns** -- `FormView`, `PaginatedView` (jump buttons + go-to-page modal), `TabView`, `WizardView`
86
+
87
+ #### Components & Theming
88
+ - **Stateful Components** -- `StatefulButton`, `StatefulSelect`, plus composites (`ConfirmationButtons`, `ToggleGroup`, `ProgressBar`) and behavioral wrappers (loading, confirmation, cooldown)
89
+ - **Form Validation** -- Built-in validators (`min_length`, `max_length`, `regex`, `choices`, `min_value`, `max_value`) with per-field error reporting
90
+ - **Theming** -- Global registry with per-view overrides; built-in default, dark, and light themes
91
+
92
+ #### Infrastructure
93
+ - **Persistence** -- `setup_persistence()` for data + view survival across restarts; pluggable backends (JSON, SQLite, Redis)
94
+ - **DevTools** -- Built-in state inspector with paginated embed output
95
+
96
+ ---
97
+
98
+ ## Quick Start
99
+
100
+ > [!TIP]
101
+ > Install from PyPI, then `import cascadeui` -- the package name on PyPI is `pycascadeui` but the import stays `cascadeui`.
102
+
103
+ ```bash
104
+ pip install pycascadeui
105
+
106
+ # Optional backends
107
+ pip install pycascadeui[sqlite] # SQLite persistence via aiosqlite
108
+ pip install pycascadeui[redis] # Redis persistence
109
+ ```
110
+
111
+ **Requirements**: Python 3.10+ | discord.py 2.7+
112
+
113
+ <br>
114
+
115
+ ### A Counter in 30 Lines
116
+
117
+ ```python
118
+ import copy, discord
119
+ from discord.ext import commands
120
+ from cascadeui import StatefulView, StatefulButton, cascade_reducer
121
+
122
+ @cascade_reducer("COUNTER_UPDATED")
123
+ async def counter_reducer(action, state):
124
+ new_state = copy.deepcopy(state)
125
+ new_state.setdefault("application", {}).setdefault("counters", {})
126
+ vid = action["payload"]["view_id"]
127
+ new_state["application"]["counters"][vid] = action["payload"]["value"]
128
+ return new_state
129
+
130
+ class CounterView(StatefulView):
131
+ def __init__(self, context):
132
+ super().__init__(context=context)
133
+ self.count = 0
134
+ self.add_item(StatefulButton(label="+1", style=discord.ButtonStyle.primary, callback=self.increment))
135
+ self.add_item(StatefulButton(label="-1", style=discord.ButtonStyle.danger, callback=self.decrement))
136
+ self.add_exit_button()
137
+
138
+ async def _update(self, interaction, delta):
139
+ await interaction.response.defer()
140
+ self.count += delta
141
+ await self.dispatch("COUNTER_UPDATED", {"view_id": self.id, "value": self.count})
142
+ if self.message:
143
+ await self.message.edit(embed=discord.Embed(title="Counter", description=f"Value: {self.count}"), view=self)
144
+
145
+ async def increment(self, interaction): await self._update(interaction, 1)
146
+ async def decrement(self, interaction): await self._update(interaction, -1)
147
+
148
+ bot = commands.Bot(command_prefix="!", intents=discord.Intents.default())
149
+
150
+ @bot.hybrid_command()
151
+ async def counter(ctx):
152
+ view = CounterView(context=ctx)
153
+ await view.send(embed=discord.Embed(title="Counter", description="Value: 0"))
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Feature Showcase
159
+
160
+ ### Navigation Stack
161
+
162
+ > Push and pop views to build multi-level menus. Each level edits the same message.
163
+
164
+ ![Navigation Stack](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/navigation.gif)
165
+
166
+ ```python
167
+ class MainMenu(StatefulView):
168
+ async def go_settings(self, interaction):
169
+ await interaction.response.defer()
170
+ new_view = await self.push(SettingsView, interaction)
171
+ await interaction.edit_original_response(embed=new_view.build_embed(), view=new_view)
172
+
173
+ class SettingsView(StatefulView):
174
+ async def go_back(self, interaction):
175
+ await interaction.response.defer()
176
+ prev_view = await self.pop(interaction)
177
+ await interaction.edit_original_response(embed=prev_view.build_embed(), view=prev_view)
178
+ ```
179
+
180
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/views/#navigation-stack)
181
+
182
+ ---
183
+
184
+ ### Pagination
185
+
186
+ > Build paginated views from raw data. Pages above `jump_threshold` (default 5) get first/last buttons and a go-to-page modal.
187
+
188
+ ![Pagination](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/pagination.gif)
189
+
190
+ ```python
191
+ view = await PaginatedView.from_data(
192
+ items=all_items,
193
+ per_page=10,
194
+ formatter=lambda chunk: discord.Embed(
195
+ title="Items",
196
+ description="\n".join(str(i) for i in chunk),
197
+ ),
198
+ context=ctx,
199
+ )
200
+ await view.send()
201
+ ```
202
+
203
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/views/#paginatedview)
204
+
205
+ ---
206
+
207
+ ### Session Limiting
208
+
209
+ > Prevent duplicate views from piling up. Open `/settings` twice and the old panel closes automatically.
210
+
211
+ ![Session Limiting](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/session-limiting.gif)
212
+
213
+ ```python
214
+ class SettingsView(StatefulView):
215
+ session_limit = 1
216
+ session_scope = "user_guild"
217
+ session_policy = "replace" # or "reject"
218
+ ```
219
+
220
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/views/#session-limiting)
221
+
222
+ ---
223
+
224
+ ### Persistence
225
+
226
+ > Persistent data keeps your settings and records intact between sessions.
227
+
228
+ ![Persistent Data](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/persistence-data.gif)
229
+
230
+ > Persistent views keep their buttons working even after a full bot restart.
231
+
232
+ ![Surviving Restarts](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/persistence-restart.gif)
233
+
234
+ ```python
235
+ # In your bot's setup_hook:
236
+ async def setup_hook():
237
+ await setup_persistence(bot=bot, backend=SQLiteBackend("cascadeui.db"))
238
+
239
+ # Views with custom_id components survive restarts:
240
+ class RoleSelector(PersistentView):
241
+ def __init__(self, *args, **kwargs):
242
+ super().__init__(*args, **kwargs)
243
+ self.add_item(StatefulButton(
244
+ label="Get Role",
245
+ custom_id="roles:get", # required for persistent views
246
+ callback=self.toggle_role,
247
+ ))
248
+ ```
249
+
250
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/persistence/)
251
+
252
+ ---
253
+
254
+ ### Ticket System (Demo)
255
+
256
+ > A full support ticket system combining PersistentView, Modal with validation, PaginatedView, custom reducers, state selectors, and theming in a single cog.
257
+
258
+ ![Ticket System](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/ticket-system.gif)
259
+
260
+ ```python
261
+ class TicketPanelView(PersistentView):
262
+ subscribed_actions = {"TICKET_CREATED", "TICKET_CLOSED"}
263
+
264
+ def state_selector(self, state):
265
+ # Only re-render when open ticket count changes
266
+ tickets = state.get("application", {}).get("tickets", {}).get(guild_id, [])
267
+ return sum(1 for t in tickets if t["status"] == "open")
268
+ ```
269
+
270
+ [View full example](examples/ticket_system.py)
271
+
272
+ ---
273
+
274
+ ### Undo/Redo
275
+
276
+ > Snapshot-based undo/redo per view session. Two class attributes and you get full state history.
277
+
278
+ ```python
279
+ class NotificationSettings(StatefulView):
280
+ enable_undo = True
281
+ undo_limit = 10
282
+
283
+ async def do_undo(self, interaction):
284
+ await interaction.response.defer()
285
+ await self.undo(interaction)
286
+
287
+ async def do_redo(self, interaction):
288
+ await interaction.response.defer()
289
+ await self.redo(interaction)
290
+ ```
291
+
292
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/views/#undoredo)
293
+
294
+ ---
295
+
296
+ ### Theming
297
+
298
+ > Register themes globally, apply per-view or as a default. Embed colors update instantly.
299
+
300
+ ```python
301
+ my_theme = Theme("custom", {
302
+ "primary_color": discord.Color.purple(),
303
+ "header_emoji": ">>",
304
+ "footer_text": "My Bot",
305
+ })
306
+ register_theme(my_theme)
307
+ set_default_theme("custom")
308
+ ```
309
+
310
+ [Full guide](https://hollowthesilver.github.io/CascadeUI/guide/theming/)
311
+
312
+ ---
313
+
314
+ ## Architecture
315
+
316
+ ```
317
+ User clicks button → Interaction callback → dispatch(action)
318
+ → Middleware pipeline → Reducer (immutable state update)
319
+ → Subscribers notified (filtered) → Views re-render
320
+ ```
321
+
322
+ All state lives in a single `StateStore` singleton. **Actions** are plain dicts describing what happened. **Reducers** receive the action and return new state immutably. **Subscribers** (views) are notified when state changes, filtered by action type and state selectors so views only re-render when their relevant slice actually changes.
323
+
324
+ This is the same unidirectional data flow pattern used by Redux and similar state management libraries, adapted for Discord's interaction-driven UI model.
325
+
326
+ ---
327
+
328
+ ## Examples
329
+
330
+ Working examples in the [`examples/`](examples/) directory, each a discord.py cog:
331
+
332
+ | Example | What it covers |
333
+ |---------|---------------|
334
+ | **[counter.py](examples/counter.py)** | Basic stateful counter with custom reducer |
335
+ | **[themed_form.py](examples/themed_form.py)** | Themes, wrappers, pagination, form/modal validation |
336
+ | **[persistence.py](examples/persistence.py)** | SQLite persistence and PersistentView |
337
+ | **[navigation.py](examples/navigation.py)** | Push/pop navigation stack |
338
+ | **[state_features.py](examples/state_features.py)** | Scoping, batching, computed values, hooks |
339
+ | **[undo_redo.py](examples/undo_redo.py)** | Undo/redo with stack depth display |
340
+ | **[settings_menu.py](examples/settings_menu.py)** | Session limiting, navigation, undo/redo, theming |
341
+ | **[ticket_system.py](examples/ticket_system.py)** | PersistentView, modals, pagination, custom reducers |
342
+
343
+ ---
344
+
345
+ ## Documentation
346
+
347
+ > [!IMPORTANT]
348
+ > The full documentation site covers state management, views, components, persistence, theming, middleware, devtools, and API reference.
349
+
350
+ **[hollowthesilver.github.io/CascadeUI](https://hollowthesilver.github.io/CascadeUI/)**
351
+
352
+ ---
353
+
354
+ ## Development
355
+
356
+ ```bash
357
+ git clone https://github.com/HollowTheSilver/CascadeUI.git
358
+ cd CascadeUI
359
+ pip install -e ".[dev]"
360
+
361
+ pytest tests/ -v # run tests
362
+ black --line-length 100 cascadeui/ # format
363
+ isort --profile black --line-length 100 cascadeui/ # sort imports
364
+ ```
365
+
366
+ ---
367
+
368
+ <p align="center">
369
+ MIT License -- see <a href="LICENSE">LICENSE</a> for details.
370
+ </p>