zeon 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.
- zeon-0.1.0/PKG-INFO +15 -0
- zeon-0.1.0/main.py +308 -0
- zeon-0.1.0/pyproject.toml +28 -0
- zeon-0.1.0/setup.cfg +4 -0
- zeon-0.1.0/zeon.egg-info/PKG-INFO +15 -0
- zeon-0.1.0/zeon.egg-info/SOURCES.txt +8 -0
- zeon-0.1.0/zeon.egg-info/dependency_links.txt +1 -0
- zeon-0.1.0/zeon.egg-info/entry_points.txt +2 -0
- zeon-0.1.0/zeon.egg-info/requires.txt +7 -0
- zeon-0.1.0/zeon.egg-info/top_level.txt +1 -0
zeon-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zeon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool to scaffold a FastAPI project structure
|
|
5
|
+
Author-email: Vinyas Bharadwaj <vinyasbharadwaj101@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: fastapi
|
|
10
|
+
Requires-Dist: uvicorn
|
|
11
|
+
Requires-Dist: sqlalchemy
|
|
12
|
+
Requires-Dist: passlib[bcrypt]
|
|
13
|
+
Requires-Dist: pyJWT
|
|
14
|
+
Requires-Dist: python-dotenv
|
|
15
|
+
Requires-Dist: pydantic
|
zeon-0.1.0/main.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import secrets
|
|
4
|
+
import textwrap
|
|
5
|
+
|
|
6
|
+
main_py_content = textwrap.dedent("""\
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from .database import Base, engine
|
|
9
|
+
from .routers import auth
|
|
10
|
+
|
|
11
|
+
app = FastAPI()
|
|
12
|
+
|
|
13
|
+
Base.metadata.create_all(bind=engine)
|
|
14
|
+
|
|
15
|
+
app.include_router(auth.router)
|
|
16
|
+
|
|
17
|
+
@app.get("/")
|
|
18
|
+
def home():
|
|
19
|
+
return {"message": "Hello world"}
|
|
20
|
+
""")
|
|
21
|
+
|
|
22
|
+
database_py_content = textwrap.dedent("""\
|
|
23
|
+
from sqlalchemy import create_engine
|
|
24
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
25
|
+
from sqlalchemy.orm import sessionmaker
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
|
|
29
|
+
|
|
30
|
+
engine = create_engine(SQLALCHEMY_DATABASE_URL)
|
|
31
|
+
|
|
32
|
+
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
|
33
|
+
|
|
34
|
+
Base = declarative_base()
|
|
35
|
+
|
|
36
|
+
def init_db():
|
|
37
|
+
Base.metadata.create_all(bind=engine)
|
|
38
|
+
|
|
39
|
+
def get_db():
|
|
40
|
+
db = SessionLocal()
|
|
41
|
+
try:
|
|
42
|
+
yield db
|
|
43
|
+
finally:
|
|
44
|
+
db.close()
|
|
45
|
+
""")
|
|
46
|
+
|
|
47
|
+
models_py_content = textwrap.dedent("""\
|
|
48
|
+
from sqlalchemy import Column, Integer, String, Text
|
|
49
|
+
from sqlalchemy.sql.sqltypes import TIMESTAMP
|
|
50
|
+
from sqlalchemy.sql import func
|
|
51
|
+
from .database import Base
|
|
52
|
+
|
|
53
|
+
class User(Base):
|
|
54
|
+
__tablename__ = "users"
|
|
55
|
+
|
|
56
|
+
id = Column(Integer, primary_key=True, nullable=False)
|
|
57
|
+
username = Column(String, nullable=False, unique=True)
|
|
58
|
+
email = Column(String, nullable=False, unique=True)
|
|
59
|
+
password = Column(String, nullable=False)
|
|
60
|
+
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), nullable=False)
|
|
61
|
+
""")
|
|
62
|
+
|
|
63
|
+
oauth2_py_content = textwrap.dedent("""\
|
|
64
|
+
from fastapi import Depends, status, HTTPException
|
|
65
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
66
|
+
import jwt
|
|
67
|
+
from jwt import PyJWTError
|
|
68
|
+
from datetime import datetime, timedelta, timezone
|
|
69
|
+
from sqlalchemy.orm import Session
|
|
70
|
+
from dotenv import load_dotenv
|
|
71
|
+
import os
|
|
72
|
+
from .schemas import TokenData
|
|
73
|
+
from .database import get_db
|
|
74
|
+
from .models import User
|
|
75
|
+
|
|
76
|
+
load_dotenv()
|
|
77
|
+
|
|
78
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='auth/login')
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
SECRET_KEY = os.getenv('SECRET_KEY')
|
|
82
|
+
ALGORITHM = os.getenv('ALGORITHM')
|
|
83
|
+
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES'))
|
|
84
|
+
|
|
85
|
+
def create_access_token(data: dict):
|
|
86
|
+
to_encode = data.copy()
|
|
87
|
+
|
|
88
|
+
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
89
|
+
to_encode.update({"exp": expire})
|
|
90
|
+
|
|
91
|
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
92
|
+
|
|
93
|
+
return encoded_jwt
|
|
94
|
+
|
|
95
|
+
def verify_access_token(token: str, credentials_exception):
|
|
96
|
+
try:
|
|
97
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
98
|
+
id: str = payload.get("user_id")
|
|
99
|
+
username: str = payload.get("username")
|
|
100
|
+
|
|
101
|
+
token_data = TokenData(id=id)
|
|
102
|
+
|
|
103
|
+
except PyJWTError:
|
|
104
|
+
raise credentials_exception
|
|
105
|
+
|
|
106
|
+
return token_data
|
|
107
|
+
|
|
108
|
+
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
|
|
109
|
+
credentials_exception = HTTPException(status_code=status.HTTP_403_FORBIDDEN,
|
|
110
|
+
detail="could not validate credentials",
|
|
111
|
+
headers={"WWW-Authenticate": "Bearer"})
|
|
112
|
+
|
|
113
|
+
token = verify_access_token(token, credentials_exception)
|
|
114
|
+
|
|
115
|
+
user = db.query(User).filter(User.id == token.id).first()
|
|
116
|
+
|
|
117
|
+
return user
|
|
118
|
+
""")
|
|
119
|
+
|
|
120
|
+
schemas_py_content = textwrap.dedent("""\
|
|
121
|
+
from pydantic import BaseModel, EmailStr
|
|
122
|
+
from datetime import datetime
|
|
123
|
+
from typing import Optional
|
|
124
|
+
|
|
125
|
+
class CreateUser(BaseModel):
|
|
126
|
+
username: str
|
|
127
|
+
email: EmailStr
|
|
128
|
+
password: str
|
|
129
|
+
|
|
130
|
+
class ResponseUser(BaseModel):
|
|
131
|
+
id: int
|
|
132
|
+
username: str
|
|
133
|
+
email: str
|
|
134
|
+
created_at: datetime
|
|
135
|
+
|
|
136
|
+
model_config = {
|
|
137
|
+
"from_attributes": True
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class Token(BaseModel):
|
|
141
|
+
access_token: str
|
|
142
|
+
token_type: str
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TokenData(BaseModel):
|
|
146
|
+
id: Optional[int] = None
|
|
147
|
+
username: Optional[str] = None
|
|
148
|
+
""")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
utils_py_content = textwrap.dedent("""\
|
|
152
|
+
from passlib.context import CryptContext
|
|
153
|
+
|
|
154
|
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Hashing and security
|
|
158
|
+
def hash(password: str):
|
|
159
|
+
return pwd_context.hash(password)
|
|
160
|
+
|
|
161
|
+
def verify(plain_password, hashed_password):
|
|
162
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
163
|
+
""")
|
|
164
|
+
|
|
165
|
+
routers_auth_py_content = textwrap.dedent("""\
|
|
166
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
167
|
+
from fastapi.security.oauth2 import OAuth2PasswordRequestForm
|
|
168
|
+
from sqlalchemy.orm import Session
|
|
169
|
+
from ..database import get_db
|
|
170
|
+
from ..schemas import Token
|
|
171
|
+
from ..models import User
|
|
172
|
+
from ..oauth2 import create_access_token
|
|
173
|
+
from ..utils import hash, verify
|
|
174
|
+
from ..schemas import CreateUser, ResponseUser
|
|
175
|
+
|
|
176
|
+
router = APIRouter(
|
|
177
|
+
prefix='/auth',
|
|
178
|
+
tags=['auth']
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@router.post('/register', response_model=ResponseUser, status_code=status.HTTP_201_CREATED)
|
|
182
|
+
def create_user(user: CreateUser, db: Session = Depends(get_db)):
|
|
183
|
+
hashed_password = hash(user.password)
|
|
184
|
+
user.password = hashed_password
|
|
185
|
+
|
|
186
|
+
new_user = User(**user.model_dump())
|
|
187
|
+
db.add(new_user)
|
|
188
|
+
db.commit()
|
|
189
|
+
db.refresh(new_user)
|
|
190
|
+
|
|
191
|
+
return new_user
|
|
192
|
+
|
|
193
|
+
@router.get('/user/{id}', response_model=ResponseUser)
|
|
194
|
+
def get_user(id: int, db: Session = Depends(get_db)):
|
|
195
|
+
user = db.query(User).filter(User.id == id).first()
|
|
196
|
+
|
|
197
|
+
if user is None:
|
|
198
|
+
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"user with id: {id} does not exist")
|
|
199
|
+
|
|
200
|
+
return user
|
|
201
|
+
|
|
202
|
+
@router.post('/login', response_model=Token)
|
|
203
|
+
def login(user_credentials: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
|
204
|
+
|
|
205
|
+
user = db.query(User).filter(User.username == user_credentials.username).first()
|
|
206
|
+
|
|
207
|
+
if user is None:
|
|
208
|
+
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"user with username: {user_credentials.username} does not exist")
|
|
209
|
+
|
|
210
|
+
if not verify(user_credentials.password, user.password):
|
|
211
|
+
raise HTTPException(status.HTTP_403_FORBIDDEN, detail=f"invalid credentials")
|
|
212
|
+
|
|
213
|
+
access_token = create_access_token(data={"user_id": user.id, "username": user.username })
|
|
214
|
+
|
|
215
|
+
return {"access_token": access_token, "token_type": "bearer"}
|
|
216
|
+
|
|
217
|
+
""")
|
|
218
|
+
|
|
219
|
+
gitignore_content = textwrap.dedent("""\
|
|
220
|
+
venv/
|
|
221
|
+
__pycache__/
|
|
222
|
+
.env
|
|
223
|
+
""")
|
|
224
|
+
|
|
225
|
+
secret_key = secrets.token_urlsafe(32)
|
|
226
|
+
env_content = textwrap.dedent(f"""\
|
|
227
|
+
SECRET_KEY={secret_key}
|
|
228
|
+
ALGORITHM=HS256
|
|
229
|
+
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
|
230
|
+
""")
|
|
231
|
+
|
|
232
|
+
requirements_content = textwrap.dedent("""\
|
|
233
|
+
annotated-types==0.7.0
|
|
234
|
+
anyio==4.9.0
|
|
235
|
+
bcrypt==4.0.1
|
|
236
|
+
certifi==2025.4.26
|
|
237
|
+
click==8.2.1
|
|
238
|
+
colorama==0.4.6
|
|
239
|
+
dnspython==2.7.0
|
|
240
|
+
email_validator==2.2.0
|
|
241
|
+
fastapi==0.115.12
|
|
242
|
+
fastapi-cli==0.0.7
|
|
243
|
+
greenlet==3.2.3
|
|
244
|
+
h11==0.16.0
|
|
245
|
+
httpcore==1.0.9
|
|
246
|
+
httptools==0.6.4
|
|
247
|
+
httpx==0.28.1
|
|
248
|
+
idna==3.10
|
|
249
|
+
Jinja2==3.1.6
|
|
250
|
+
markdown-it-py==3.0.0
|
|
251
|
+
MarkupSafe==3.0.2
|
|
252
|
+
mdurl==0.1.2
|
|
253
|
+
passlib==1.7.4
|
|
254
|
+
pydantic==2.11.5
|
|
255
|
+
pydantic_core==2.33.2
|
|
256
|
+
Pygments==2.19.1
|
|
257
|
+
PyJWT==2.10.1
|
|
258
|
+
python-dotenv==1.1.0
|
|
259
|
+
python-multipart==0.0.20
|
|
260
|
+
PyYAML==6.0.2
|
|
261
|
+
rich==14.0.0
|
|
262
|
+
rich-toolkit==0.14.7
|
|
263
|
+
shellingham==1.5.4
|
|
264
|
+
sniffio==1.3.1
|
|
265
|
+
SQLAlchemy==2.0.41
|
|
266
|
+
starlette==0.46.2
|
|
267
|
+
typer==0.16.0
|
|
268
|
+
typing-inspection==0.4.1
|
|
269
|
+
typing_extensions==4.14.0
|
|
270
|
+
uvicorn==0.34.3
|
|
271
|
+
watchfiles==1.0.5
|
|
272
|
+
websockets==15.0.1
|
|
273
|
+
""")
|
|
274
|
+
|
|
275
|
+
def create_structure(project_name: str):
|
|
276
|
+
base_path = Path(project_name)
|
|
277
|
+
app_path = base_path / "app"
|
|
278
|
+
routers_path = app_path / "routers"
|
|
279
|
+
app_path.mkdir(parents=True, exist_ok=True)
|
|
280
|
+
routers_path.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
|
|
282
|
+
(base_path / ".gitignore").write_text(gitignore_content)
|
|
283
|
+
(base_path / ".env").write_text(env_content)
|
|
284
|
+
|
|
285
|
+
(app_path / "__init__.py").touch()
|
|
286
|
+
(app_path / "main.py").write_text(main_py_content)
|
|
287
|
+
(app_path / "database.py").write_text(database_py_content)
|
|
288
|
+
(app_path / "models.py").write_text(models_py_content)
|
|
289
|
+
(app_path / "schemas.py").write_text(schemas_py_content)
|
|
290
|
+
(app_path / "utils.py").write_text(utils_py_content)
|
|
291
|
+
(app_path / "oauth2.py").write_text(oauth2_py_content)
|
|
292
|
+
|
|
293
|
+
(routers_path / "__init__.py").touch()
|
|
294
|
+
(routers_path / "auth.py").write_text(routers_auth_py_content)
|
|
295
|
+
|
|
296
|
+
(base_path / "requirements.txt").write_text(requirements_content)
|
|
297
|
+
|
|
298
|
+
print(f"✅ Your FastAPI project '{project_name}' created successfully.")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def main():
|
|
302
|
+
parser = argparse.ArgumentParser(description="Initialize a FastAPI project.")
|
|
303
|
+
parser.add_argument("name", help="Name of the project")
|
|
304
|
+
args = parser.parse_args()
|
|
305
|
+
create_structure(args.name)
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zeon"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CLI tool to scaffold a FastAPI project structure"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Vinyas Bharadwaj", email = "vinyasbharadwaj101@gmail.com" }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
"fastapi",
|
|
15
|
+
"uvicorn",
|
|
16
|
+
"sqlalchemy",
|
|
17
|
+
"passlib[bcrypt]",
|
|
18
|
+
"pyJWT",
|
|
19
|
+
"python-dotenv",
|
|
20
|
+
"pydantic",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
zeon = "main:main"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
zeon-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zeon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI tool to scaffold a FastAPI project structure
|
|
5
|
+
Author-email: Vinyas Bharadwaj <vinyasbharadwaj101@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: fastapi
|
|
10
|
+
Requires-Dist: uvicorn
|
|
11
|
+
Requires-Dist: sqlalchemy
|
|
12
|
+
Requires-Dist: passlib[bcrypt]
|
|
13
|
+
Requires-Dist: pyJWT
|
|
14
|
+
Requires-Dist: python-dotenv
|
|
15
|
+
Requires-Dist: pydantic
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
main
|