mvcfw 0.1.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.
mvcfw-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: mvcfw
3
+ Version: 0.1.0
4
+ Summary: MVCFW - Python Desktop MVC Framework
5
+ Author: Ritesh
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: customtkinter
8
+ Requires-Dist: SQLAlchemy
9
+ Requires-Dist: watchdog
mvcfw-0.1.0/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # MVCFW
2
+
3
+ MVCFW is a Python framework for building desktop applications using the **Model-View-Controller (MVC)** architecture.
4
+
5
+ **Version:** 0.1.0
6
+ **Created and maintained by Ritesh Kashyap**
7
+
8
+ ## Features
9
+
10
+ * MVC architecture
11
+ * Hierarchical routing
12
+ * Master pages / layouts
13
+ * SQLAlchemy-based ORM
14
+ * Database migrations
15
+ * Reusable UI components
16
+ * Project and application scaffolding
17
+ * Development server with auto-reload
18
+ * Command-line interface
19
+
20
+ ## Requirements
21
+
22
+ * Python 3.10+
23
+ * CustomTkinter
24
+ * SQLAlchemy
25
+ * Watchdog
26
+
27
+ ## Installation
28
+
29
+ Install MVCFW with pip:
30
+
31
+ ```bash
32
+ pip install mvcfw
33
+ ```
34
+
35
+ For development installation from source:
36
+
37
+ ```bash
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Create a Project
42
+
43
+ Create a new MVCFW project:
44
+
45
+ ```bash
46
+ mvcfw startproject MyProject
47
+ ```
48
+
49
+ Move into the project:
50
+
51
+ ```bash
52
+ cd MyProject
53
+ ```
54
+
55
+ Run the application:
56
+
57
+ ```bash
58
+ python run.py
59
+ ```
60
+
61
+ ## Create an Application
62
+
63
+ Inside your project:
64
+
65
+ ```bash
66
+ python manager.py startapp students
67
+ ```
68
+
69
+ This generates:
70
+
71
+ ```text
72
+ students/
73
+ ├── models.py
74
+ ├── controller.py
75
+ ├── router.py
76
+ ├── __init__.py
77
+ └── views/
78
+ ├── index.py
79
+ └── __init__.py
80
+ ```
81
+
82
+ The application is automatically added to `INSTALLED_APPS`.
83
+
84
+ ## Routing
85
+
86
+ MVCFW uses hierarchical routing.
87
+
88
+ Project-level routing connects an application router:
89
+
90
+ ```python
91
+ ROUTING={
92
+ "Main":app_router.ROUTING
93
+ }
94
+ ```
95
+
96
+ Routes can then be accessed using:
97
+
98
+ ```python
99
+ routingjunction.goto(["Main","index"])
100
+ ```
101
+
102
+ ## Master Pages
103
+
104
+ Views can use a master page/layout:
105
+
106
+ ```python
107
+ from fwfiles.UI.decorators import use_masterpage
108
+
109
+ @use_masterpage("app.views.layout.Layout_default")
110
+ class Index(ctk.CTkFrame):
111
+ ...
112
+ ```
113
+
114
+ The master page provides a reusable layout while the view renders its content inside the layout body.
115
+
116
+ ## ORM
117
+
118
+ MVCFW provides a simple SQLAlchemy-based ORM.
119
+
120
+ Example:
121
+
122
+ ```python
123
+ from fwfiles.ORM.base import Model
124
+ from fwfiles.ORM import feilds
125
+
126
+ class Student(Model):
127
+ __tablename__="students"
128
+
129
+ id=feilds.IntegerField(primary_key=True,increment=True)
130
+ name=feilds.CharField(max_length=255)
131
+ email=feilds.CharField(max_length=255)
132
+ ```
133
+
134
+ Basic operations include:
135
+
136
+ ```python
137
+ Student.all()
138
+ Student.filter(name="Ritesh")
139
+
140
+ student.save()
141
+ student.delete()
142
+ ```
143
+
144
+ ## Database Migrations
145
+
146
+ Create migrations:
147
+
148
+ ```bash
149
+ python manager.py makemigrations
150
+ ```
151
+
152
+ Apply migrations:
153
+
154
+ ```bash
155
+ python manager.py migrate
156
+ ```
157
+
158
+ Reset the database:
159
+
160
+ ```bash
161
+ python manager.py reset_db
162
+ ```
163
+
164
+ View installed applications:
165
+
166
+ ```bash
167
+ python manager.py show_apps
168
+ ```
169
+
170
+ ## Development Server
171
+
172
+ Run the development server:
173
+
174
+ ```bash
175
+ python manager.py runserver
176
+ ```
177
+
178
+ MVCFW uses `watchdog` for detecting file changes during development.
179
+
180
+ ## Reference Application
181
+
182
+ Generate the MVCFW reference application:
183
+
184
+ ```bash
185
+ mvcfw start_ref_app
186
+ ```
187
+
188
+ The reference application demonstrates:
189
+
190
+ * MVC
191
+ * Routing
192
+ * Master pages
193
+ * ORM
194
+ * Database migrations
195
+ * Reusable UI components
196
+
197
+ ## CLI
198
+
199
+ Check the framework version:
200
+
201
+ ```bash
202
+ mvcfw --version
203
+ ```
204
+
205
+ You can also use:
206
+
207
+ ```bash
208
+ mvcfw version
209
+ mvcfw -v
210
+ ```
211
+
212
+ Create a project:
213
+
214
+ ```bash
215
+ mvcfw startproject <project_name>
216
+ ```
217
+
218
+ Create the reference application:
219
+
220
+ ```bash
221
+ mvcfw start_ref_app
222
+ ```
223
+
224
+ ## Project Commands
225
+
226
+ Inside a project:
227
+
228
+ ```bash
229
+ python manager.py startapp <app_name>
230
+ python manager.py makemigrations
231
+ python manager.py migrate
232
+ python manager.py reset_db
233
+ python manager.py show_apps
234
+ python manager.py runserver
235
+ ```
236
+
237
+ ## Project Structure
238
+
239
+ A project created with `startproject` has the following structure:
240
+
241
+ ```text
242
+ MyProject/
243
+ ├── app/
244
+ │ ├── models.py
245
+ │ ├── controller.py
246
+ │ ├── router.py
247
+ │ └── views/
248
+ ├── migrations/
249
+ ├── MyProject/
250
+ │ ├── config.py
251
+ │ └── router.py
252
+ ├── manager.py
253
+ └── run.py
254
+ ```
255
+
256
+ ## Architecture
257
+
258
+ MVCFW follows the Model-View-Controller architecture:
259
+
260
+ ```text
261
+ Model
262
+
263
+ Controller
264
+
265
+ View
266
+ ```
267
+
268
+ Routing connects routes to controllers, while master pages provide reusable UI layouts.
269
+
270
+ ## Author
271
+
272
+ **Ritesh Kashyap**
273
+
274
+ MVCFW is an open-source project focused on making desktop application development with Python and MVC architecture simpler.
File without changes
File without changes
@@ -0,0 +1,106 @@
1
+ from tkinter import ttk
2
+ import customtkinter as ctk
3
+ from fwfiles import routingjunction
4
+
5
+
6
+
7
+ class Card(ctk.CTkFrame):
8
+ def __init__(
9
+ self, master, title="Card Title", content="Card Content...", button_text=None, button_command=None, **kwargs):
10
+ super().__init__(master, corner_radius=10, **kwargs)
11
+
12
+ self.title_label = ctk.CTkLabel(self, text=title,font=("Helvetica", 16, "bold"))
13
+ self.title_label.pack(pady=(10, 0), padx=10, anchor="w")
14
+
15
+ self.content_label = ctk.CTkLabel(self, text=content)
16
+ self.content_label.pack(pady=(0, 5), padx=10, anchor="w")
17
+
18
+ self.button = None
19
+
20
+ if button_text:
21
+ self.button = ctk.CTkButton( self, text=button_text, command=button_command)
22
+ self.button.pack(pady=10, padx=10, anchor="e", fill="both")
23
+
24
+
25
+
26
+
27
+
28
+ class InfoCard(ctk.CTkFrame):
29
+ def __init__(self, master, title="Card Title", content="Card Content...", **kwargs):
30
+ super().__init__(master, corner_radius=10, **kwargs)
31
+
32
+ self.title_label = ctk.CTkLabel(self, text=title, font=("Helvetica", 16, "bold"))
33
+ self.title_label.pack(pady=(10, 0), padx=10, anchor="w")
34
+
35
+ self.content_label = ctk.CTkLabel(self, text=content)
36
+ self.content_label.pack(pady=(0, 5), padx=10, anchor="w")
37
+
38
+
39
+
40
+
41
+ class Table(ctk.CTkFrame):
42
+ def __init__(self, master, headings=None, data=None, height=12, **kwargs):
43
+ super().__init__(master, corner_radius=10, **kwargs)
44
+
45
+ self.headings = headings or []
46
+ self.data = data or []
47
+
48
+ style = ttk.Style()
49
+ style.configure("Treeview", font=("Roboto Narrow", 10))
50
+ style.configure("Treeview.Heading", font=("Roboto", 10, "bold"))
51
+
52
+ self.table = ttk.Treeview(self, columns=self.headings, show="headings", height=height)
53
+
54
+ for col in self.headings:
55
+ self.table.heading(col, text=col)
56
+ self.table.column(col, anchor="w")
57
+
58
+ self.table.pack(padx=10, pady=10, anchor="w", fill="both", expand=True)
59
+
60
+ self.refresh(self.data)
61
+
62
+ def refresh(self, data):
63
+ self.clear()
64
+
65
+ for row in data:
66
+ self.table.insert("", "end", values=row)
67
+
68
+ def clear(self):
69
+ for item in self.table.get_children():
70
+ self.table.delete(item)
71
+
72
+
73
+ class LeftSidebar(ctk.CTkFrame):
74
+ def __init__(self, master, items=None, width=200, fg_color=None, **kwargs):
75
+ super().__init__(master, width=width, fg_color=fg_color, corner_radius=0, **kwargs)
76
+ self.items = items or []
77
+ self.create_items()
78
+
79
+ def create_items(self):
80
+ for item in self.items:
81
+ text = item.get("text", "")
82
+ command = item.get("command")
83
+
84
+ button = ctk.CTkButton(self, fg_color="#ebebeb", text_color="#292828", text=text, command=command)
85
+
86
+ button.pack(padx=5, pady=5, fill="x")
87
+
88
+ class CardGrid(ctk.CTkFrame):
89
+ def __init__(self, master, cards=None, card_width=250, padx=10, pady=10, fg_color="#ebebeb", **kwargs):
90
+ super().__init__(master, fg_color=fg_color, **kwargs)
91
+ self.cards, self.card_width, self.padx, self.pady = cards or [], card_width, padx, pady
92
+ self.bind("<Configure>", self.arrange_cards)
93
+
94
+ def arrange_cards(self, event=None):
95
+ cols = max(1, self.winfo_width() // self.card_width)
96
+ for i, card in enumerate(self.cards): card.grid(row=i // cols, column=i % cols, padx=self.padx, pady=self.pady, sticky="nsew")
97
+ for c in range(cols): self.grid_columnconfigure(c, weight=1)
98
+
99
+ def add_card(self, card): self.cards.append(card); self.arrange_cards()
100
+ def clear(self):
101
+ for card in self.cards: card.grid_forget()
102
+ self.cards.clear()
103
+
104
+
105
+
106
+
@@ -0,0 +1,29 @@
1
+ from .db import Base, DBconnection
2
+
3
+ db=DBconnection()
4
+
5
+ class Model(Base):
6
+ __abstract__=True
7
+
8
+
9
+ @classmethod
10
+ def create_table(cls):
11
+ db.create_tables()
12
+
13
+ @classmethod
14
+ def all(cls):
15
+ return db.session.query(cls).all()
16
+
17
+ @classmethod
18
+ def filter(cls, **kwargs):
19
+ return db.session.query(cls).filter_by(**kwargs).all()
20
+
21
+ def save(self):
22
+ db.session.add(self)
23
+ db.session.commit()
24
+
25
+ def delete(self):
26
+ db.session.delete(self)
27
+ db.session.commit()
28
+
29
+
@@ -0,0 +1,12 @@
1
+ from .migration import make_migrations, apply_migrations, reset_db
2
+ from .generalcommands import show_apps, runserver, startapp
3
+
4
+ def handle_command(command, app_name=None):
5
+ if command == "makemigrations": make_migrations()
6
+ elif command == "migrate": apply_migrations()
7
+ elif command == "reset_db": reset_db()
8
+ elif command == "show_apps": show_apps()
9
+ elif command == "runserver": runserver()
10
+ elif command=="startapp": startapp(app_name)
11
+ else: print(f"Unknown command: {command}")
12
+
@@ -0,0 +1,13 @@
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.orm import sessionmaker, declarative_base
3
+
4
+ Base = declarative_base()
5
+
6
+ class DBconnection:
7
+ def __init__(self, url="sqlite:///db.sqlite3", echo=True):
8
+ self.engine = create_engine(url, echo=echo)
9
+ self.Session = sessionmaker(bind=self.engine)
10
+ self.session = self.Session()
11
+
12
+ def create_tables(self):
13
+ Base.metadata.create_all(self.engine)
@@ -0,0 +1,40 @@
1
+ from sqlalchemy import Column, Integer, String, Float, Boolean, Text, Date, DateTime, Time
2
+ from sqlalchemy.dialects.mysql import LONGTEXT
3
+ from datetime import datetime
4
+
5
+
6
+ def IntegerField(primary_key=False, null=False, increment=False):
7
+ return Column(Integer, primary_key=primary_key, nullable=null,autoincrement=increment)
8
+
9
+ def CharField(max_length=255, null=False):
10
+ return Column(String(max_length), nullable=null)
11
+
12
+ def FloatField(null=False):
13
+ return Column(Float, nullable=null)
14
+
15
+ def BooleanField(default=False, null=False):
16
+ return Column(Boolean, default=default, nullable=null)
17
+
18
+ def TextField(long=False, null=False):
19
+ return Column(LONGTEXT if long else Text, nullable=null)
20
+
21
+ def FileField(max_length=255, null=False):
22
+ return Column(String(max_length), nullable=null)
23
+
24
+ def DateField(auto_now=False, auto_now_add=False, null=False):
25
+ default = None
26
+ if auto_now or auto_now_add:
27
+ default = datetime.utcnow().date
28
+ return Column(Date, default=default, nullable=null)
29
+
30
+ def DateTimeField(auto_now=False, auto_now_add=False, null=False):
31
+ default = None
32
+ if auto_now or auto_now_add:
33
+ default = datetime.utcnow
34
+ return Column(DateTime, default=default, nullable=null)
35
+
36
+ def TimeField(auto_now=False, auto_now_add=False, null=False):
37
+ default = None
38
+ if auto_now or auto_now_add:
39
+ default = datetime.utcnow().time
40
+ return Column(Time, default=default, nullable=null)
@@ -0,0 +1,121 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import subprocess
5
+ from watchdog.observers import Observer
6
+ from watchdog.events import FileSystemEventHandler
7
+ from fwfiles import projectcontext
8
+
9
+ def show_apps():
10
+ config = projectcontext.get_config()
11
+ for i in config.INSTALLED_APPS:
12
+ print(i)
13
+
14
+ def startapp(name):
15
+ if not name:
16
+ print("Usage: python manager.py startapp <app_name>")
17
+ return
18
+
19
+ if os.path.exists(name):
20
+ print(f"Error: App '{name}' already exists.")
21
+ return
22
+
23
+ os.makedirs(os.path.join(name,"views"))
24
+ os.makedirs(os.path.join("migrations",name))
25
+
26
+ files={
27
+ "__init__.py":"",
28
+ "models.py":"",
29
+ "controller.py":"""from .views import index
30
+
31
+ def start(req):
32
+ return index.Index
33
+ """,
34
+ "router.py":"""from . import controller
35
+
36
+ ROUTING={
37
+ "index":controller.start
38
+ }
39
+ """,
40
+ os.path.join("views","__init__.py"):"from . import index\n",
41
+ os.path.join("views","index.py"):'''import customtkinter as ctk
42
+
43
+ class Index(ctk.CTkFrame):
44
+ def __init__(self,master,data=None,**kwargs):
45
+ super().__init__(master,**kwargs)
46
+ ctk.CTkLabel(self,text="Welcome",font=("Arial",24,"bold")).pack(pady=40)
47
+ '''
48
+ }
49
+
50
+ for path,content in files.items():
51
+ with open(os.path.join(name,path),"w",encoding="utf-8") as f:
52
+ f.write(content)
53
+
54
+ config=projectcontext.get_config()
55
+
56
+ if name not in config.INSTALLED_APPS:
57
+ config.INSTALLED_APPS.append(name)
58
+
59
+ config_path=config.__file__
60
+
61
+ with open(config_path,"w",encoding="utf-8") as f:
62
+ f.write(f"INSTALLED_APPS={config.INSTALLED_APPS!r}\n\n")
63
+ f.write(f"THEME={config.THEME!r}\n\n")
64
+ f.write(f"DATABASE={config.DATABASE!r}\n")
65
+
66
+ print(f"App '{name}' created successfully.")
67
+
68
+
69
+
70
+ class RunServer:
71
+ def __init__(self, project_dir=None, entry_script="run.py"):
72
+ self.project_dir = project_dir or os.getcwd()
73
+ self.entry_script = entry_script
74
+ self.process = None
75
+
76
+ def start_process(self):
77
+ if self.process:
78
+ self.process.kill()
79
+ self.process.wait()
80
+ print(f"Starting {self.entry_script} ...")
81
+ self.process = subprocess.Popen([sys.executable, self.entry_script])
82
+
83
+ def on_any_event(self, event):
84
+ if event.src_path.endswith(".py"):
85
+ print(f"[Hot-Reload] Change detected: {event.src_path}")
86
+ self.start_process()
87
+
88
+ def run(self):
89
+ from watchdog.observers import Observer
90
+ from watchdog.events import FileSystemEventHandler
91
+
92
+ class Handler(FileSystemEventHandler):
93
+ def __init__(self, server):
94
+ self.server = server
95
+ def on_any_event(self, event):
96
+ self.server.on_any_event(event)
97
+
98
+ # Start observer
99
+ handler = Handler(self)
100
+ observer = Observer()
101
+ observer.schedule(handler, self.project_dir, recursive=True)
102
+ observer.start()
103
+
104
+ # Start initial process
105
+ self.start_process()
106
+ print(f"Watching {self.project_dir} for changes...")
107
+
108
+ try:
109
+ while True:
110
+ time.sleep(1)
111
+ except KeyboardInterrupt:
112
+ observer.stop()
113
+ if self.process:
114
+ self.process.kill()
115
+ observer.join()
116
+
117
+
118
+ # ----------------- Command line entry -----------------
119
+ def runserver():
120
+ server = RunServer()
121
+ server.run()