pylara 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 (162) hide show
  1. pylara-1.0.0/LICENSE +21 -0
  2. pylara-1.0.0/MANIFEST.in +6 -0
  3. pylara-1.0.0/PKG-INFO +86 -0
  4. pylara-1.0.0/README.md +58 -0
  5. pylara-1.0.0/docs/cheatsheet.md +62 -0
  6. pylara-1.0.0/docs/configuration.md +84 -0
  7. pylara-1.0.0/docs/console.md +85 -0
  8. pylara-1.0.0/docs/container.md +80 -0
  9. pylara-1.0.0/docs/contributing.md +57 -0
  10. pylara-1.0.0/docs/controllers.md +87 -0
  11. pylara-1.0.0/docs/csrf.md +112 -0
  12. pylara-1.0.0/docs/database.md +75 -0
  13. pylara-1.0.0/docs/deployment.md +110 -0
  14. pylara-1.0.0/docs/errors.md +99 -0
  15. pylara-1.0.0/docs/helpers.md +87 -0
  16. pylara-1.0.0/docs/index.md +51 -0
  17. pylara-1.0.0/docs/installation.md +72 -0
  18. pylara-1.0.0/docs/lifecycle.md +42 -0
  19. pylara-1.0.0/docs/limitations.md +58 -0
  20. pylara-1.0.0/docs/middleware.md +123 -0
  21. pylara-1.0.0/docs/migrations.md +129 -0
  22. pylara-1.0.0/docs/orm.md +191 -0
  23. pylara-1.0.0/docs/providers.md +55 -0
  24. pylara-1.0.0/docs/query-builder.md +90 -0
  25. pylara-1.0.0/docs/requests.md +81 -0
  26. pylara-1.0.0/docs/responses.md +91 -0
  27. pylara-1.0.0/docs/routing.md +165 -0
  28. pylara-1.0.0/docs/sessions.md +161 -0
  29. pylara-1.0.0/docs/structure.md +69 -0
  30. pylara-1.0.0/docs/testing.md +135 -0
  31. pylara-1.0.0/docs/uploads.md +119 -0
  32. pylara-1.0.0/docs/validation.md +78 -0
  33. pylara-1.0.0/docs/views.md +160 -0
  34. pylara-1.0.0/examples/demo/README.md +19 -0
  35. pylara-1.0.0/examples/demo/app/__init__.py +0 -0
  36. pylara-1.0.0/examples/demo/app/controllers/__init__.py +0 -0
  37. pylara-1.0.0/examples/demo/app/controllers/auth_controller.py +60 -0
  38. pylara-1.0.0/examples/demo/app/controllers/post_controller.py +40 -0
  39. pylara-1.0.0/examples/demo/app/controllers/user_controller.py +39 -0
  40. pylara-1.0.0/examples/demo/app/middleware/__init__.py +0 -0
  41. pylara-1.0.0/examples/demo/app/models/__init__.py +0 -0
  42. pylara-1.0.0/examples/demo/app/models/post.py +11 -0
  43. pylara-1.0.0/examples/demo/app/models/user.py +12 -0
  44. pylara-1.0.0/examples/demo/bootstrap/__init__.py +0 -0
  45. pylara-1.0.0/examples/demo/bootstrap/app.py +53 -0
  46. pylara-1.0.0/examples/demo/config/app.json +4 -0
  47. pylara-1.0.0/examples/demo/database/__init__.py +0 -0
  48. pylara-1.0.0/examples/demo/database/migrations/2024_01_01_000000_create_users_table.py +16 -0
  49. pylara-1.0.0/examples/demo/database/migrations/2024_01_01_000001_create_posts_table.py +17 -0
  50. pylara-1.0.0/examples/demo/database/migrations/__init__.py +0 -0
  51. pylara-1.0.0/examples/demo/manage.py +24 -0
  52. pylara-1.0.0/examples/demo/routes/__init__.py +0 -0
  53. pylara-1.0.0/examples/demo/routes/web.py +62 -0
  54. pylara-1.0.0/examples/tracker/.env.example +10 -0
  55. pylara-1.0.0/examples/tracker/.gitignore +4 -0
  56. pylara-1.0.0/examples/tracker/README.md +17 -0
  57. pylara-1.0.0/examples/tracker/app/__init__.py +0 -0
  58. pylara-1.0.0/examples/tracker/app/controllers/__init__.py +0 -0
  59. pylara-1.0.0/examples/tracker/app/controllers/auth_controller.py +41 -0
  60. pylara-1.0.0/examples/tracker/app/controllers/task_controller.py +58 -0
  61. pylara-1.0.0/examples/tracker/app/controllers/welcome_controller.py +10 -0
  62. pylara-1.0.0/examples/tracker/app/middleware/__init__.py +0 -0
  63. pylara-1.0.0/examples/tracker/app/models/__init__.py +0 -0
  64. pylara-1.0.0/examples/tracker/app/models/task.py +7 -0
  65. pylara-1.0.0/examples/tracker/app/models/user.py +7 -0
  66. pylara-1.0.0/examples/tracker/app/providers/__init__.py +0 -0
  67. pylara-1.0.0/examples/tracker/app/providers/app_service_provider.py +11 -0
  68. pylara-1.0.0/examples/tracker/bootstrap/__init__.py +0 -0
  69. pylara-1.0.0/examples/tracker/bootstrap/app.py +34 -0
  70. pylara-1.0.0/examples/tracker/config/app.json +4 -0
  71. pylara-1.0.0/examples/tracker/database/migrations/2026_11_15_031555_create_tasks_table.py +18 -0
  72. pylara-1.0.0/examples/tracker/database/migrations/2026_11_15_031555_create_users_table.py +16 -0
  73. pylara-1.0.0/examples/tracker/manage.py +17 -0
  74. pylara-1.0.0/examples/tracker/public/css/app.css +11 -0
  75. pylara-1.0.0/examples/tracker/requirements.txt +1 -0
  76. pylara-1.0.0/examples/tracker/resources/views/auth/login.html +11 -0
  77. pylara-1.0.0/examples/tracker/resources/views/auth/register.html +15 -0
  78. pylara-1.0.0/examples/tracker/resources/views/layouts/app.html +15 -0
  79. pylara-1.0.0/examples/tracker/resources/views/tasks/index.html +28 -0
  80. pylara-1.0.0/examples/tracker/resources/views/welcome.html +11 -0
  81. pylara-1.0.0/examples/tracker/routes/__init__.py +0 -0
  82. pylara-1.0.0/examples/tracker/routes/api.py +10 -0
  83. pylara-1.0.0/examples/tracker/routes/web.py +21 -0
  84. pylara-1.0.0/examples/tracker/storage/.gitignore +2 -0
  85. pylara-1.0.0/examples/tracker/tests/__init__.py +6 -0
  86. pylara-1.0.0/examples/tracker/tests/test_example.py +36 -0
  87. pylara-1.0.0/examples/tracker/tests/test_tracker.py +135 -0
  88. pylara-1.0.0/examples/tracker/wsgi.py +11 -0
  89. pylara-1.0.0/pylara/__init__.py +23 -0
  90. pylara-1.0.0/pylara/__main__.py +6 -0
  91. pylara-1.0.0/pylara/application.py +297 -0
  92. pylara-1.0.0/pylara/cli.py +108 -0
  93. pylara-1.0.0/pylara/console/__init__.py +0 -0
  94. pylara-1.0.0/pylara/console/console.py +422 -0
  95. pylara-1.0.0/pylara/database/__init__.py +0 -0
  96. pylara-1.0.0/pylara/database/migration.py +305 -0
  97. pylara-1.0.0/pylara/http/__init__.py +3 -0
  98. pylara-1.0.0/pylara/http/context.py +17 -0
  99. pylara-1.0.0/pylara/http/controller.py +35 -0
  100. pylara-1.0.0/pylara/http/exceptions.py +38 -0
  101. pylara-1.0.0/pylara/http/kernel.py +86 -0
  102. pylara-1.0.0/pylara/http/request.py +271 -0
  103. pylara-1.0.0/pylara/http/response.py +110 -0
  104. pylara-1.0.0/pylara/http/uploads.py +81 -0
  105. pylara-1.0.0/pylara/middleware/__init__.py +0 -0
  106. pylara-1.0.0/pylara/middleware/middleware.py +106 -0
  107. pylara-1.0.0/pylara/middleware/session.py +93 -0
  108. pylara-1.0.0/pylara/orm/__init__.py +0 -0
  109. pylara-1.0.0/pylara/orm/model.py +515 -0
  110. pylara-1.0.0/pylara/routing/__init__.py +0 -0
  111. pylara-1.0.0/pylara/routing/router.py +289 -0
  112. pylara-1.0.0/pylara/stubs/project/.env.example.stub +10 -0
  113. pylara-1.0.0/pylara/stubs/project/.env.stub +11 -0
  114. pylara-1.0.0/pylara/stubs/project/.gitignore.stub +4 -0
  115. pylara-1.0.0/pylara/stubs/project/README.md.stub +42 -0
  116. pylara-1.0.0/pylara/stubs/project/app/__init__.py.stub +0 -0
  117. pylara-1.0.0/pylara/stubs/project/app/controllers/__init__.py.stub +0 -0
  118. pylara-1.0.0/pylara/stubs/project/app/controllers/welcome_controller.py.stub +10 -0
  119. pylara-1.0.0/pylara/stubs/project/app/middleware/__init__.py.stub +0 -0
  120. pylara-1.0.0/pylara/stubs/project/app/models/__init__.py.stub +0 -0
  121. pylara-1.0.0/pylara/stubs/project/app/models/user.py.stub +7 -0
  122. pylara-1.0.0/pylara/stubs/project/app/providers/__init__.py.stub +0 -0
  123. pylara-1.0.0/pylara/stubs/project/app/providers/app_service_provider.py.stub +11 -0
  124. pylara-1.0.0/pylara/stubs/project/bootstrap/__init__.py.stub +0 -0
  125. pylara-1.0.0/pylara/stubs/project/bootstrap/app.py.stub +34 -0
  126. pylara-1.0.0/pylara/stubs/project/config/app.json.stub +4 -0
  127. pylara-1.0.0/pylara/stubs/project/database/migrations/__MIGRATION_TS___create_users_table.py.stub +16 -0
  128. pylara-1.0.0/pylara/stubs/project/manage.py.stub +17 -0
  129. pylara-1.0.0/pylara/stubs/project/public/css/app.css.stub +11 -0
  130. pylara-1.0.0/pylara/stubs/project/requirements.txt.stub +1 -0
  131. pylara-1.0.0/pylara/stubs/project/resources/views/layouts/app.html.stub +14 -0
  132. pylara-1.0.0/pylara/stubs/project/resources/views/welcome.html.stub +11 -0
  133. pylara-1.0.0/pylara/stubs/project/routes/__init__.py.stub +0 -0
  134. pylara-1.0.0/pylara/stubs/project/routes/api.py.stub +10 -0
  135. pylara-1.0.0/pylara/stubs/project/routes/web.py.stub +10 -0
  136. pylara-1.0.0/pylara/stubs/project/storage/.gitignore.stub +2 -0
  137. pylara-1.0.0/pylara/stubs/project/tests/__init__.py.stub +6 -0
  138. pylara-1.0.0/pylara/stubs/project/tests/test_example.py.stub +36 -0
  139. pylara-1.0.0/pylara/stubs/project/wsgi.py.stub +11 -0
  140. pylara-1.0.0/pylara/support/__init__.py +0 -0
  141. pylara-1.0.0/pylara/support/config.py +55 -0
  142. pylara-1.0.0/pylara/support/hashing.py +32 -0
  143. pylara-1.0.0/pylara/support/helpers.py +218 -0
  144. pylara-1.0.0/pylara/support/service_container.py +100 -0
  145. pylara-1.0.0/pylara/support/service_provider.py +23 -0
  146. pylara-1.0.0/pylara/support/session.py +206 -0
  147. pylara-1.0.0/pylara/support/view.py +261 -0
  148. pylara-1.0.0/pylara/testing.py +141 -0
  149. pylara-1.0.0/pylara/version.py +3 -0
  150. pylara-1.0.0/pylara.egg-info/PKG-INFO +86 -0
  151. pylara-1.0.0/pylara.egg-info/SOURCES.txt +160 -0
  152. pylara-1.0.0/pylara.egg-info/dependency_links.txt +1 -0
  153. pylara-1.0.0/pylara.egg-info/entry_points.txt +2 -0
  154. pylara-1.0.0/pylara.egg-info/top_level.txt +1 -0
  155. pylara-1.0.0/pyproject.toml +48 -0
  156. pylara-1.0.0/setup.cfg +4 -0
  157. pylara-1.0.0/tests/__init__.py +0 -0
  158. pylara-1.0.0/tests/__main__.py +9 -0
  159. pylara-1.0.0/tests/test_docs.py +229 -0
  160. pylara-1.0.0/tests/test_features.py +739 -0
  161. pylara-1.0.0/tests/test_framework.py +639 -0
  162. pylara-1.0.0/tests/test_security.py +472 -0
pylara-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The PyLara Authors
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,6 @@
1
+ include LICENSE README.md
2
+ recursive-include pylara/stubs *
3
+ recursive-include docs *.md
4
+ graft tests
5
+ graft examples
6
+ global-exclude __pycache__ *.py[cod] *.sqlite
pylara-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylara
3
+ Version: 1.0.0
4
+ Summary: A Laravel-inspired web framework for Python: routing, Eloquent-style ORM, middleware, migrations and an Artisan-style CLI.
5
+ Author-email: Dzulfikar Maulana <dzulfikar.maulana@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gekif/pylara
8
+ Project-URL: Documentation, https://github.com/gekif/pylara/tree/main/docs
9
+ Project-URL: Source, https://github.com/gekif/pylara
10
+ Project-URL: Issues, https://github.com/gekif/pylara/issues
11
+ Keywords: laravel,framework,web,wsgi,orm,mvc,artisan
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # PyLara
30
+
31
+ A Laravel-inspired web framework for Python. Routing, an Eloquent-style ORM, middleware,
32
+ migrations, a small template engine, validation, sessions, CSRF protection, file uploads, and an
33
+ Artisan-style CLI, with **no dependencies** beyond the standard library.
34
+
35
+ ```bash
36
+ python -m pip install pylara
37
+ pylara new blog
38
+ cd blog
39
+ python -m manage migrate
40
+ python -m manage serve
41
+ ```
42
+
43
+ Open http://127.0.0.1:8000.
44
+
45
+ > **Status:** 1.0 is the first release. PyLara is synchronous (WSGI), targets SQLite, and is
46
+ > best suited to learning, prototypes, internal tools and small APIs. Read
47
+ > [Limitations](https://github.com/YOUR-USER/pylara/blob/main/docs/limitations.md) before choosing it for production.
48
+
49
+ ## A taste of PyLara
50
+
51
+ ```python
52
+ from pylara import Application, Controller, Model
53
+
54
+ class Task(Model):
55
+ table = "tasks"
56
+ fillable = ["title", "done"]
57
+
58
+ class TaskController(Controller):
59
+ def index(self, request):
60
+ return self.json([t.to_dict() for t in Task.where("done", 0).order_by("id", "DESC").get()])
61
+
62
+ def store(self, request):
63
+ data = request.validate({"title": "required|string|min:3|max:150"})
64
+ return self.json(Task.create(title=data["title"], done=0).to_dict(), 201)
65
+
66
+ app = Application()
67
+ app.group({"prefix": "/api"}, lambda r: [r.resource("tasks", TaskController)])
68
+ ```
69
+
70
+ ## Documentation
71
+
72
+ Start at **[docs/index.md](https://github.com/YOUR-USER/pylara/blob/main/docs/index.md)**: installation, configuration, routing, controllers,
73
+ requests, responses, views, validation, the database layer and ORM, testing, deployment and
74
+ publishing your own package.
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ git clone <your fork> && cd pylara
80
+ python -m pip install -e .
81
+ python -m tests # run the whole test suite
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT. See [LICENSE](https://github.com/YOUR-USER/pylara/blob/main/LICENSE).
pylara-1.0.0/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # PyLara
2
+
3
+ A Laravel-inspired web framework for Python. Routing, an Eloquent-style ORM, middleware,
4
+ migrations, a small template engine, validation, sessions, CSRF protection, file uploads, and an
5
+ Artisan-style CLI, with **no dependencies** beyond the standard library.
6
+
7
+ ```bash
8
+ python -m pip install pylara
9
+ pylara new blog
10
+ cd blog
11
+ python -m manage migrate
12
+ python -m manage serve
13
+ ```
14
+
15
+ Open http://127.0.0.1:8000.
16
+
17
+ > **Status:** 1.0 is the first release. PyLara is synchronous (WSGI), targets SQLite, and is
18
+ > best suited to learning, prototypes, internal tools and small APIs. Read
19
+ > [Limitations](https://github.com/YOUR-USER/pylara/blob/main/docs/limitations.md) before choosing it for production.
20
+
21
+ ## A taste of PyLara
22
+
23
+ ```python
24
+ from pylara import Application, Controller, Model
25
+
26
+ class Task(Model):
27
+ table = "tasks"
28
+ fillable = ["title", "done"]
29
+
30
+ class TaskController(Controller):
31
+ def index(self, request):
32
+ return self.json([t.to_dict() for t in Task.where("done", 0).order_by("id", "DESC").get()])
33
+
34
+ def store(self, request):
35
+ data = request.validate({"title": "required|string|min:3|max:150"})
36
+ return self.json(Task.create(title=data["title"], done=0).to_dict(), 201)
37
+
38
+ app = Application()
39
+ app.group({"prefix": "/api"}, lambda r: [r.resource("tasks", TaskController)])
40
+ ```
41
+
42
+ ## Documentation
43
+
44
+ Start at **[docs/index.md](https://github.com/YOUR-USER/pylara/blob/main/docs/index.md)**: installation, configuration, routing, controllers,
45
+ requests, responses, views, validation, the database layer and ORM, testing, deployment and
46
+ publishing your own package.
47
+
48
+ ## Development
49
+
50
+ ```bash
51
+ git clone <your fork> && cd pylara
52
+ python -m pip install -e .
53
+ python -m tests # run the whole test suite
54
+ ```
55
+
56
+ ## License
57
+
58
+ MIT. See [LICENSE](https://github.com/YOUR-USER/pylara/blob/main/LICENSE).
@@ -0,0 +1,62 @@
1
+ # Laravel Cheat Sheet
2
+
3
+ | Laravel (PHP) | PyLara (Python) |
4
+ |---|---|
5
+ | `composer create-project laravel/laravel blog` | `pylara new blog` |
6
+ | `php artisan serve` | `python -m manage serve` |
7
+ | `php artisan route:list` | `python -m manage route:list` |
8
+ | `php artisan make:controller PostController --resource` | `python -m manage make:controller PostController --resource` |
9
+ | `php artisan make:model Post -m` | `python -m manage make:model Post -m` |
10
+ | `php artisan make:middleware X` | `python -m manage make:middleware X` |
11
+ | `php artisan migrate` / `migrate:rollback` / `migrate:status` | `python -m manage migrate` / `migrate:rollback` / `migrate:status` |
12
+ | `Route::get('/u/{id}', [C::class, 'show'])` | `app.get("/u/{id}", [C, "show"])` |
13
+ | `Route::resource('posts', PostController::class)` | `app.resource("posts", PostController)` |
14
+ | `Route::get(...)->name('x')` | `app.get(...).named("x")` |
15
+ | `Route::get(...)->middleware(M::class)` | `app.get(...).with_middleware(M)` |
16
+ | `Route::prefix('api')->group(fn)` | `app.group({"prefix": "/api"}, fn)` |
17
+ | `route('x', ['id' => 1])` | `route("x", id=1)` |
18
+ | `$request->input('a')`, `$request->all()` | `request.input("a")`, `request.all()` |
19
+ | `$request->only(['a','b'])` | `request.only("a", "b")` |
20
+ | `$request->validate(['a' => 'required\|email'])` | `request.validate({"a": "required\|email"})` |
21
+ | `return response()->json($x, 201)` | `return JsonResponse(x, 201)` or `self.json(x, 201)` |
22
+ | `return redirect('/home')` | `return RedirectResponse("/home")` or `self.redirect("/home")` |
23
+ | `abort(404)` | `abort(404)` |
24
+ | `view('welcome', ['a' => 1])` | `view("welcome", a=1)` |
25
+ | Blade `{{ $x }}`, `{!! $x !!}` | `{{ x }}`, `{!! x !!}` |
26
+ | Blade `@if`, `@foreach`, `@extends`, `@section`, `@include` | `{% if %}`, `{% for %}`, `{% extends %}`, `{% block %}`, `{% include %}` |
27
+ | `config('app.name')` | `config("app.name")` |
28
+ | `env('APP_DEBUG', false)` | `env("APP_DEBUG", False)` |
29
+ | `User::find(1)`, `User::findOrFail(1)` | `User.find(1)`, `User.find_or_fail(1)` |
30
+ | `User::where('a', 1)->first()` | `User.where("a", 1).first()` |
31
+ | `User::create([...])` | `User.create(**data)` |
32
+ | `$user->save()`, `$user->delete()` | `user.save()`, `user.delete()` |
33
+ | `$fillable`, `$hidden`, `$casts`, `SoftDeletes` | `fillable`, `hidden`, `casts`, `soft_deletes = True` |
34
+ | `$this->hasMany(Post::class)` | `self.has_many(Post)` |
35
+ | `$this->belongsTo(User::class)` | `self.belongs_to(User)` |
36
+ | `User::paginate(15)` | `User.query().paginate(per_page=15, page=1)` |
37
+ | `Schema::create('t', fn (Blueprint $t) => ...)` | `Schema.create("t", lambda t: [...])` |
38
+ | `$table->string('name')->nullable()` | `t.string("name").nullable()` |
39
+ | `$table->foreignId('user_id')->constrained()` | `t.integer("user_id")` + `t.foreign("user_id").references("id").on("users")` |
40
+ | `Hash::make($p)` / `Hash::check($p, $h)` | `Hash.make(p)` / `Hash.check(p, h)` |
41
+ | `collect($items)->pluck('a')` | `collect(items).pluck("a")` |
42
+ | `session('cart')`, `session(['cart' => 1])` | `request.session.get("cart")`, `request.session.put("cart", 1)` |
43
+ | `redirect()->with('status', 'Saved')` | `request.session.flash("status", "Saved")` |
44
+ | `@csrf` | `{!! csrf_field !!}` |
45
+ | `VerifyCsrfToken` / `$except` | `VerifyCsrfToken` / `except_paths = [...]` |
46
+ | `auth` middleware | `RequireLogin` |
47
+ | `php artisan key:generate` | `python -m manage key:generate` |
48
+ | `$request->file('a')->store('dir')` | `request.file("a").save("dir")` |
49
+ | `'photo' => 'required\|image\|max:2048'` | `"photo": "required\|file\|mimes:png,jpg\|max_kb:2048"` |
50
+ | `View::share('k', $v)` | `app.share("k", v)` |
51
+ | `$this->get('/')->assertStatus(200)` | `client.get("/").status_code == 200` |
52
+ | `app()->bind(...)`, `app()->singleton(...)`, `app()->make(...)` | `app.bind(...)`, `app.singleton(...)`, `app.make(...)` |
53
+ | Service providers `register()` / `boot()` | `ServiceProvider.register()` / `boot()` |
54
+ | `App\Http\Middleware\X` `handle($request, Closure $next)` | `class X(Middleware): def handle(self, request, next)` |
55
+
56
+ ## Concepts that differ
57
+
58
+ - **Parameters are named after your function arguments**, and arrive as strings.
59
+ - **No facades**: you import functions and classes directly.
60
+ - **`fillable` is required** for mass assignment (closed by default).
61
+ - **`except` is `except_`** because `except` is a Python keyword.
62
+ - **No Eloquent magic properties for relations**: call the relationship method, `user.posts()`.
@@ -0,0 +1,84 @@
1
+ # Configuration
2
+
3
+ PyLara has two configuration mechanisms: **environment variables** (`.env`) for things that
4
+ change between machines, and **JSON config files** in `config/` for everything else.
5
+
6
+ ## Environment configuration
7
+
8
+ When the `Application` is created it loads `.env` from the project root (the `base_path`).
9
+
10
+ ```dotenv
11
+ APP_NAME="Blog"
12
+ APP_ENV=local
13
+ APP_DEBUG=true
14
+ DB_DATABASE=storage/database.sqlite
15
+ ```
16
+
17
+ Rules:
18
+
19
+ - Lines starting with `#` are comments. A value may be quoted (`"two words"`). An unquoted value
20
+ may end with ` # comment`.
21
+ - **Real environment variables always win** over `.env`. `DB_DATABASE=:memory: python -m manage migrate`
22
+ overrides the file.
23
+ - Never commit `.env`; commit `.env.example` instead. New projects already contain both, and
24
+ `.env` is in `.gitignore`.
25
+
26
+ ### Reading values
27
+
28
+ ```python
29
+ app.env("APP_NAME", "PyLara") # on the application
30
+ from pylara.support.helpers import env
31
+ env("APP_DEBUG", False) # helper (uses the most recently created application)
32
+ ```
33
+
34
+ `env()` converts the strings `true`, `false`, `null` and `empty` (also `(true)`, `(false)`,
35
+ `(null)`, `(empty)`) into `True`, `False`, `None` and `""`, like Laravel. Everything else is
36
+ returned as a string, so convert numbers yourself: `int(env("WORKERS", 2))`.
37
+
38
+ ### Variables PyLara itself understands
39
+
40
+ | Variable | Used by | Meaning |
41
+ |---|---|---|
42
+ | `APP_DEBUG` | error handling | `true` shows tracebacks in the browser. **Must be `false` in production.** |
43
+ | `DB_DATABASE` | `bootstrap/app.py` (generated project) | SQLite file path, or `:memory:` |
44
+ | `APP_KEY` | [sessions](sessions.md) | secret that signs session cookies. Create it with `python -m manage key:generate`. |
45
+ | `SESSION_DRIVER` | sessions | `file` (default) or `memory` |
46
+ | `SESSION_LIFETIME` | sessions | minutes of inactivity before a session expires (default `120`) |
47
+ | `SESSION_SECURE` | sessions | `true` on HTTPS: the cookie is never sent over plain HTTP |
48
+ | `MAX_BODY_MB` | requests | largest accepted request body in megabytes (default `10`); larger requests get 413 |
49
+ | `APP_NAME`, `APP_ENV` | your code | Free to use. The welcome page shows `APP_NAME`. |
50
+
51
+ Add your own freely (`MAIL_HOST`, `STRIPE_KEY`, ...).
52
+
53
+ ## Configuration files
54
+
55
+ Every `config/<file>.json` is loaded on demand and read with **dot notation**: the first segment
56
+ is the file name.
57
+
58
+ `config/app.json`
59
+ ```json
60
+ { "name": "Blog", "per_page": 15, "mail": { "host": "smtp.example.com" } }
61
+ ```
62
+
63
+ ```python
64
+ app.config.get("app.per_page") # 15
65
+ app.config.get("app.mail.host") # "smtp.example.com"
66
+ app.config.get("app.missing", "fallback") # "fallback"
67
+ app.config.set("app.per_page", 25) # runtime override (in memory only)
68
+
69
+ from pylara.support.helpers import config
70
+ config("app.name")
71
+ ```
72
+
73
+ JSON files cannot read the environment. If a value depends on the machine, use `env()` in
74
+ `bootstrap/app.py` or a provider instead.
75
+
76
+ ## Debug mode
77
+
78
+ With `APP_DEBUG=true` an unhandled exception renders a page with the traceback, and JSON requests
79
+ receive the exception message. With `APP_DEBUG=false` users only see a generic "Server Error".
80
+ **In both modes the traceback is printed to stderr**, so your server log always has it.
81
+
82
+ ## Database configuration
83
+
84
+ The database connection is created in `bootstrap/app.py`; see [Database](database.md).
@@ -0,0 +1,85 @@
1
+ # Console (Command Line)
2
+
3
+ PyLara has two command line entry points.
4
+
5
+ | Command | Where | Purpose |
6
+ |---|---|---|
7
+ | `pylara new <name>` | anywhere | create a new application |
8
+ | `python -m manage <command>` | inside a project | everything else (the equivalent of `php artisan`) |
9
+
10
+ `python -m pylara ...` is the same as `pylara ...` and works even when the scripts folder is not on
11
+ your `PATH`.
12
+
13
+ ## Built-in commands
14
+
15
+ Run `python -m manage` (or `python -m manage help`) to list them.
16
+
17
+ | Command | Description |
18
+ |---|---|
19
+ | `serve [--host H] [--port P]` | development server (default 127.0.0.1:8000) |
20
+ | `key:generate [--force] [--show]` | create `APP_KEY` in `.env` (signs session cookies) |
21
+ | `route:list` | table of methods, URIs, route names and actions |
22
+ | `make:controller Name [--resource]` | create `app/controllers/<name>.py` |
23
+ | `make:model Name [-m]` | create `app/models/<name>.py` (and a migration with `-m`) |
24
+ | `make:middleware Name` | create `app/middleware/<name>.py` |
25
+ | `make:migration name` | create a timestamped file in `database/migrations/` |
26
+ | `migrate` | run pending migrations |
27
+ | `migrate:status` | list migrations as Ran / Pending |
28
+ | `migrate:rollback [--steps N]` | undo the last N batches (default 1) |
29
+
30
+ Generators refuse to overwrite an existing file, and names must be plain identifiers (letters,
31
+ digits, `_`), so `make:controller ../x` is rejected. Exit codes: `0` success, `1` error.
32
+
33
+ ## Custom commands
34
+
35
+ ```python
36
+ # app/commands/greet.py
37
+ from pylara.console.console import Command, CommandError, load_app
38
+
39
+
40
+ class GreetCommand(Command):
41
+ name = "greet"
42
+ description = "Say hello"
43
+
44
+ def configure(self, parser): # an argparse.ArgumentParser
45
+ parser.add_argument("name")
46
+ parser.add_argument("--shout", action="store_true")
47
+
48
+ def handle(self, args):
49
+ if not args.name.strip():
50
+ raise CommandError("Name must not be empty") # prints an error, exit code 1
51
+ text = f"Hello, {args.name}!"
52
+ self.info(text.upper() if args.shout else text)
53
+ return 0
54
+ ```
55
+
56
+ Register it in `manage.py`:
57
+
58
+ ```python
59
+ from app.commands.greet import GreetCommand
60
+ console.register(GreetCommand())
61
+ ```
62
+
63
+ ```bash
64
+ python -m manage greet Ana --shout
65
+ ```
66
+
67
+ Output helpers on `Command`: `info()` (green), `warn()` (yellow), `error()` (red, stderr), `line()`.
68
+ Colors are used only on a terminal and are disabled when `NO_COLOR` is set.
69
+
70
+ ### Using the application inside a command
71
+
72
+ ```python
73
+ def handle(self, args):
74
+ app = load_app() # imports bootstrap.app from the current directory
75
+ ... # models are ready to use, the database is connected
76
+ ```
77
+
78
+ `load_app()` raises a friendly `CommandError` outside a project.
79
+
80
+ ## Running the console from code
81
+
82
+ ```python
83
+ from pylara.console.console import Console
84
+ exit_code = Console().run(["migrate:status"])
85
+ ```
@@ -0,0 +1,80 @@
1
+ # Service Container
2
+
3
+ The service container is a registry that knows how to build objects. The `Application` **is** a
4
+ container, so `app.bind(...)`, `app.make(...)` and friends are all available.
5
+
6
+ ## Binding
7
+
8
+ ```python
9
+ class Mailer:
10
+ def __init__(self, host): self.host = host
11
+
12
+ # A factory that runs every time make() is called
13
+ app.bind("mailer", lambda: Mailer("smtp.example.com"))
14
+
15
+ # A factory that runs once; the result is reused
16
+ app.singleton("mailer", lambda: Mailer("smtp.example.com"))
17
+
18
+ # An object you already built
19
+ app.instance("mailer", Mailer("smtp.example.com"))
20
+ ```
21
+
22
+ **Factory contract.** A factory that accepts a parameter receives the container; a factory with no
23
+ parameters is called bare:
24
+
25
+ ```python
26
+ app.singleton("mailer", lambda app: Mailer(app.env("MAIL_HOST", "localhost")))
27
+ ```
28
+
29
+ You may bind by name (`"mailer"`) or by class. Classes are stored under their class name:
30
+
31
+ ```python
32
+ app.singleton(Mailer, lambda app: Mailer(app.env("MAIL_HOST")))
33
+ app.make(Mailer) # same as app.make("Mailer")
34
+ ```
35
+
36
+ ## Resolving
37
+
38
+ ```python
39
+ mailer = app.make("mailer") # KeyError if nothing is bound
40
+ app.bound("mailer") # True / False
41
+ app["mailer"] # shorthand for make()
42
+ ```
43
+
44
+ `app.make("app")`, `app.make("router")` and `app.make("config")` are pre-registered.
45
+
46
+ ## Automatic injection
47
+
48
+ `app.build(SomeClass)` instantiates a class and fills its constructor from **type hints**: each
49
+ annotated parameter whose type is bound in the container is resolved. Parameters with a default
50
+ value use it. Anything else raises a `KeyError` that names the parameter.
51
+
52
+ ```python
53
+ class Reports:
54
+ def __init__(self, mailer: Mailer, limit: int = 10):
55
+ self.mailer, self.limit = mailer, limit
56
+
57
+ reports = app.build(Reports)
58
+ ```
59
+
60
+ Controllers get this treatment automatically: the router builds them with `app.build`.
61
+
62
+ ```python
63
+ class ReportController(Controller):
64
+ def __init__(self, mailer: Mailer):
65
+ self.mailer = mailer
66
+
67
+ def send(self, request):
68
+ ...
69
+ ```
70
+
71
+ Register `Mailer` in a [service provider](providers.md) and the controller receives it.
72
+
73
+ ## Binding is not global state
74
+
75
+ Everything is attached to the application object you create, so tests can build a fresh
76
+ `Application`, bind fakes, and run against it:
77
+
78
+ ```python
79
+ app.instance("mailer", FakeMailer())
80
+ ```
@@ -0,0 +1,57 @@
1
+ # Contributing
2
+
3
+ Thank you for helping improve PyLara.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone <your fork>
9
+ cd pylara
10
+ python -m venv .venv
11
+ .venv\Scripts\activate # Windows (Linux/macOS: source .venv/bin/activate)
12
+ python -m pip install -e .
13
+ python -m tests
14
+ ```
15
+
16
+ `python -m tests` runs both suites:
17
+
18
+ - `tests/test_framework.py` is the original script-style suite for the core (ORM, router, request,
19
+ response, middleware, migrations, collections).
20
+ - `tests/test_features.py` is a `unittest` suite for 1.0 features, including integration tests that
21
+ generate a project with `pylara new` and run it.
22
+
23
+ Run one file or test: `python -m unittest tests.test_features.RoutingTest -v`.
24
+
25
+ ## Layout
26
+
27
+ ```
28
+ pylara/
29
+ ├── application.py Application: container, env, static files, WSGI, dev server
30
+ ├── cli.py / __main__.py global `pylara` command (pylara new)
31
+ ├── version.py the only place the version is written
32
+ ├── testing.py TestClient
33
+ ├── http/ request, response, kernel (middleware + errors), controller, exceptions
34
+ ├── routing/router.py Route, Router
35
+ ├── middleware/ Middleware base and built-ins
36
+ ├── orm/model.py QueryBuilder and Model
37
+ ├── database/migration.py Blueprint, Schema, Migrator, discover
38
+ ├── console/console.py manage.py commands
39
+ ├── support/ container, config, helpers/Collection, hashing, view engine, providers
40
+ └── stubs/project/ files copied by `pylara new` (*.stub; __UPPER__ tokens are replaced)
41
+ examples/demo/ a small users/posts/auth demo application
42
+ ```
43
+
44
+ ## Guidelines
45
+
46
+ - Keep PyLara **dependency-free** (standard library only).
47
+ - Every behaviour change needs a test, and the documentation in `docs/` must stay accurate: if you
48
+ change an API, change its page.
49
+ - Be conservative with security: values are always SQL parameters, column names go through the
50
+ identifier check, HTML output is escaped, static paths cannot leave `public/`.
51
+ - Run the suite with warnings as errors: `python -W error -m tests`.
52
+ - Files in `pylara/stubs/project` end with `.stub` so they are not treated as Python modules when
53
+ the package is installed. New stub files are picked up automatically.
54
+
55
+ ## Releasing
56
+
57
+ See [Publishing to PyPI](publishing.md).
@@ -0,0 +1,87 @@
1
+ # Controllers
2
+
3
+ Controllers group request-handling logic into classes.
4
+
5
+ ## Generating a controller
6
+
7
+ ```bash
8
+ python -m manage make:controller PostController # one index() method
9
+ python -m manage make:controller PostController --resource # CRUD methods
10
+ ```
11
+
12
+ This writes `app/controllers/post_controller.py` and prints the import line to use.
13
+
14
+ ## Writing controllers
15
+
16
+ ```python
17
+ from pylara import Controller, Request
18
+ from app.models.post import Post
19
+
20
+
21
+ class PostController(Controller):
22
+
23
+ def index(self, request: Request):
24
+ return self.json([p.to_dict() for p in Post.all()])
25
+
26
+ def show(self, request: Request, id: str):
27
+ return self.json(Post.find_or_fail(int(id)).to_dict())
28
+
29
+ def store(self, request: Request):
30
+ data = request.validate({"title": "required|string|max:150", "body": "required"})
31
+ return self.json(Post.create(**data).to_dict(), 201)
32
+ ```
33
+
34
+ Every action receives the `request` first, followed by the route parameters it declares (by
35
+ name). Route them:
36
+
37
+ ```python
38
+ app.get("/posts/{id}", [PostController, "show"])
39
+ app.resource("posts", PostController)
40
+ ```
41
+
42
+ ## What you can return
43
+
44
+ A `Response`, a `dict`/`list` (JSON), a `str` (HTML) or `None`. See
45
+ [Responses](responses.md).
46
+
47
+ ## Helpers on the base class
48
+
49
+ | Method | Does |
50
+ |---|---|
51
+ | `self.json(data, status=200)` | JSON response |
52
+ | `self.view(name, data=None, status=200, **context)` | render a [view](views.md) |
53
+ | `self.response(content="", status=200)` | plain response |
54
+ | `self.redirect(url, status=302)` | redirect |
55
+ | `self.abort(status, message="")` | stop with an HTTP error |
56
+ | `self.not_found(message="Not Found")` | `abort(404)` |
57
+ | `self.unauthorized(message="Unauthorized")` | `abort(401)` |
58
+
59
+ ## Dependency injection
60
+
61
+ Controllers are created through the [container](container.md), so type-hinted constructor
62
+ parameters that are bound in the container are injected:
63
+
64
+ ```python
65
+ class ReportController(Controller):
66
+ def __init__(self, mailer: Mailer):
67
+ self.mailer = mailer
68
+ ```
69
+
70
+ A new controller instance is built **for every request**.
71
+
72
+ ## Resource controllers
73
+
74
+ Implement any of `index create store show edit update destroy` and call `app.resource(...)`. See
75
+ [Routing](routing.md#resource-routes). The controller may implement only the methods it needs.
76
+
77
+ ## Single-action routes
78
+
79
+ Not every endpoint needs a class: a function or lambda is a perfectly good action.
80
+
81
+ ```python
82
+ @app.post("/subscribe")
83
+ def subscribe(request):
84
+ data = request.validate({"email": "required|email"})
85
+ ...
86
+ return {"subscribed": data["email"]}
87
+ ```