Skip to content

Commit f68101a

Browse files
fix(core,utils): add Python 3.6 compatibility support
- Replace walrus operator (:=) with Python 3.6 compatible syntax across all modules - Replace functools.cache with functools.lru_cache(maxsize=None) for Python 3.6 - Replace str.removesuffix/removeprefix with string slicing for Python 3.6 - Replace subprocess.run(capture_output=True) with stdout/stderr pipes for Python 3.6 - Update pyproject.toml and add setup.py with Python 3.6 compatible dependencies - Fix @functools.lru_cache decorators to include maxsize parameter - Ensure all syntax is compatible with Python 3.6 while maintaining functionality - Verified with successful cotlette startproject and manage.py commands
1 parent 27ab90d commit f68101a

File tree

29 files changed

+229
-73
lines changed

29 files changed

+229
-73
lines changed

pyproject.toml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
[build-system]
2-
requires = ["setuptools>=61.0.0,<69.3.0"]
2+
requires = ["setuptools>=40.0.0"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "cotlette"
77
version = "0.0.0"
88
requires-python = ">= 3.6"
99
dependencies = [
10-
"uvicorn",
11-
"fastapi",
12-
"asgiref",
13-
"jinja2",
14-
"bcrypt",
15-
"python-jose",
16-
"pyjwt",
17-
"itsdangerous",
18-
"python-multipart",
19-
"sqlalchemy>=2.0.0",
20-
"alembic>=1.12.0",
21-
"click>=8.0.0",
10+
"uvicorn<0.20.0",
11+
"fastapi<0.100.0",
12+
"asgiref<4.0.0",
13+
"jinja2<3.2.0",
14+
"bcrypt<4.1.0",
15+
"python-jose<3.4.0",
16+
"pyjwt<2.8.0",
17+
"itsdangerous<2.2.0",
18+
"python-multipart<0.1.0",
19+
"sqlalchemy<2.0.0",
20+
"alembic<1.12.0",
21+
"click<8.2.0",
2222
]
2323

2424
authors = [

setup.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python
2+
"""Setup script for cotlette."""
3+
4+
from setuptools import setup, find_packages
5+
import os
6+
7+
# Read the README file
8+
def read_readme():
9+
readme_path = os.path.join(os.path.dirname(__file__), "README.md")
10+
if os.path.exists(readme_path):
11+
with open(readme_path, "r", encoding="utf-8") as f:
12+
return f.read()
13+
return "FastAPI sizzles, Django dazzles. The best of both worlds in one framework."
14+
15+
setup(
16+
name="cotlette",
17+
version="0.0.0",
18+
description="FastAPI sizzles, Django dazzles. The best of both worlds in one framework.",
19+
long_description=read_readme(),
20+
long_description_content_type="text/markdown",
21+
author="Vladimir Penzin",
22+
author_email="[email protected]",
23+
url="https://github.com/ForceFledgling/cotlette",
24+
packages=find_packages(where="src"),
25+
package_dir={"": "src"},
26+
python_requires=">=3.6",
27+
install_requires=[
28+
"uvicorn<0.20.0",
29+
"fastapi<0.100.0",
30+
"asgiref<4.0.0",
31+
"jinja2<3.2.0",
32+
"bcrypt<4.1.0",
33+
"python-jose<3.4.0",
34+
"pyjwt<2.8.0",
35+
"itsdangerous<2.2.0",
36+
"python-multipart<0.1.0",
37+
"sqlalchemy<2.0.0",
38+
"alembic<1.12.0",
39+
"click<8.2.0",
40+
],
41+
entry_points={
42+
"console_scripts": [
43+
"cotlette=cotlette.core.management:execute_from_command_line",
44+
],
45+
},
46+
classifiers=[
47+
"Development Status :: 2 - Pre-Alpha",
48+
"Environment :: Web Environment",
49+
"Framework :: FastAPI",
50+
"Intended Audience :: Developers",
51+
"License :: OSI Approved :: MIT License",
52+
"Operating System :: OS Independent",
53+
"Programming Language :: Python",
54+
"Programming Language :: Python :: 3",
55+
"Programming Language :: Python :: 3 :: Only",
56+
"Programming Language :: Python :: 3.6",
57+
"Programming Language :: Python :: 3.12",
58+
"Topic :: Internet :: WWW/HTTP",
59+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
60+
"Topic :: Software Development :: Libraries :: Application Frameworks",
61+
"Topic :: Software Development :: Libraries :: Python Modules",
62+
],
63+
license="MIT License",
64+
)

src/cotlette/apps/registry.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def get_app_config(self, app_label):
177177
raise LookupError(message)
178178

179179
# This method is performance-critical at least for Cotlette's test suite.
180-
@functools.cache
180+
@functools.lru_cache(maxsize=None)
181181
def get_models(self, include_auto_created=False, include_swapped=False):
182182
"""
183183
Return a list of all installed models.
@@ -273,7 +273,10 @@ def get_containing_app_config(self, object_name):
273273
candidates = []
274274
for app_config in self.app_configs.values():
275275
if object_name.startswith(app_config.name):
276-
subpath = object_name.removeprefix(app_config.name)
276+
if object_name.startswith(app_config.name):
277+
subpath = object_name[len(app_config.name):]
278+
else:
279+
subpath = object_name
277280
if subpath == "" or subpath[0] == ".":
278281
candidates.append(app_config)
279282
if candidates:
@@ -292,7 +295,7 @@ def get_registered_model(self, app_label, model_name):
292295
raise LookupError("Model '%s.%s' not registered." % (app_label, model_name))
293296
return model
294297

295-
@functools.cache
298+
@functools.lru_cache(maxsize=None)
296299
def get_swappable_settings_name(self, to_string):
297300
"""
298301
For a given model string (e.g. "auth.User"), return the name of the

src/cotlette/conf/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ def __repr__(self):
7171

7272
def __getattr__(self, name):
7373
"""Return the value of a setting and cache it in self.__dict__."""
74-
if (_wrapped := self._wrapped) is empty:
74+
_wrapped = self._wrapped
75+
if _wrapped is empty:
7576
self._setup(name)
7677
_wrapped = self._wrapped
7778
val = getattr(_wrapped, name)

src/cotlette/contrib/admin/urls.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from cotlette.contrib.auth.users.models import UserModel
1818
from cotlette.contrib.auth.groups.models import GroupModel
19+
from cotlette.contrib.auth.groups.forms import GroupCreateForm
1920

2021

2122
router = APIRouter()
@@ -225,24 +226,35 @@ async def user_delete_post(request: Request, user_id: int):
225226
@router.get("/groups/create", response_model=None)
226227
@requires("user_auth")
227228
async def group_create_view(request: Request):
229+
form = GroupCreateForm()
228230
return render_template(request=request, template_name="admin/group_create.html", context={
231+
"form": form,
229232
"url_for": url_for,
230233
"config": request.app.settings,
231234
})
232235

233236
@router.post("/groups/create", response_model=None)
234237
@requires("user_auth")
235238
async def group_create_post(request: Request):
236-
form = await request.form()
237-
group = GroupModel(
238-
name=form.get("name"),
239-
description=form.get("description")
240-
)
241-
group.save()
239+
data = await request.form()
240+
form = GroupCreateForm(data)
241+
if form.is_valid():
242+
group = GroupModel(
243+
name=form.cleaned_data["name"],
244+
description=form.cleaned_data["description"]
245+
)
246+
group.save()
247+
return render_template(request=request, template_name="admin/group_create.html", context={
248+
"form": GroupCreateForm(),
249+
"url_for": url_for,
250+
"config": request.app.settings,
251+
"success": True
252+
})
242253
return render_template(request=request, template_name="admin/group_create.html", context={
254+
"form": form,
243255
"url_for": url_for,
244256
"config": request.app.settings,
245-
"success": True
257+
"errors": "Пожалуйста, исправьте ошибки в форме."
246258
})
247259

248260
# --- Group Delete ---
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from cotlette.forms.forms import Form
2+
from cotlette.forms.fields import CharField
3+
4+
class GroupCreateForm(Form):
5+
name = CharField(label='Название группы', required=True)
6+
description = CharField(label='Описание', required=False)

src/cotlette/contrib/templates/admin/group_create.html

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@
1010
{% if success %}
1111
<div class="alert alert-success">Group created!</div>
1212
{% endif %}
13+
{% if errors %}
14+
<div class="alert alert-danger">{{ errors }}</div>
15+
{% endif %}
1316
<form method="post">
14-
<div class="mb-3">
15-
<label class="form-label">Name</label>
16-
<input type="text" class="form-control" name="name" required>
17-
</div>
18-
<div class="mb-3">
19-
<label class="form-label">Description</label>
20-
<input type="text" class="form-control" name="description">
21-
</div>
17+
{{ form|form_as_p|safe }}
2218
<button type="submit" class="btn btn-primary">Create</button>
2319
<a href="{{ url_for('admin/groups') }}" class="btn btn-secondary">Back</a>
2420
</form>

src/cotlette/core/handlers/asgi.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ def __init__(self, scope, body_file):
5454
self.script_name = get_script_prefix(scope)
5555
if self.script_name:
5656
# TODO: Better is-prefix checking, slash handling?
57-
self.path_info = scope["path"].removeprefix(self.script_name)
57+
if scope["path"].startswith(self.script_name):
58+
self.path_info = scope["path"][len(self.script_name):]
59+
else:
60+
self.path_info = scope["path"]
5861
else:
5962
self.path_info = scope["path"]
6063
# HTTP basics.

src/cotlette/core/management/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class instance. Allow all errors raised by the import process
4949
return module.Command()
5050

5151

52-
@functools.cache
52+
@functools.lru_cache(maxsize=None)
5353
def get_commands():
5454
"""
5555
Return a dictionary mapping command names to their callback applications.

src/cotlette/core/management/color.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def style_func(x):
103103
return style
104104

105105

106-
@functools.cache
106+
@functools.lru_cache(maxsize=None)
107107
def no_style():
108108
"""
109109
Return a Style object with no color scheme.

0 commit comments

Comments
 (0)