├── .github
└── workflows
│ ├── cli_test.yaml
│ ├── deploy_docs.yaml
│ ├── integration_test.yaml
│ ├── release.yaml
│ ├── release_beta.yaml
│ └── tests.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── async_orm.md
├── blank.md
├── book_resource_api_docs.png
├── cli.md
├── controllers.md
├── dependency_injection.md
├── docker.md
├── getting_started.md
├── guards.md
├── imgs
│ ├── DI.png
│ ├── module-architecture.png
│ ├── pynest-logo.png
│ ├── pynest_new_logo_opt-removebg-preview.jpeg
│ └── pynest_new_logo_opt.png
├── index.html
├── introduction.md
├── license.md
├── lifespan_tasks.md
├── modules.md
├── mongodb.md
├── providers.md
├── styles
│ └── extra.css
└── sync_orm.md
├── examples
├── BlankApp
│ ├── __init__.py
│ ├── main.py
│ └── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ ├── example
│ │ ├── __init__.py
│ │ ├── example_controller.py
│ │ ├── example_model.py
│ │ ├── example_module.py
│ │ └── example_service.py
│ │ ├── product
│ │ ├── __init__.py
│ │ ├── product_controller.py
│ │ ├── product_model.py
│ │ ├── product_module.py
│ │ └── product_service.py
│ │ └── user
│ │ ├── __init__.py
│ │ ├── user_controller.py
│ │ ├── user_model.py
│ │ ├── user_module.py
│ │ └── user_service.py
├── CommandLineApp
│ ├── __init__.py
│ ├── main.py
│ └── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ └── user
│ │ ├── __init__.py
│ │ ├── user_controller.py
│ │ ├── user_module.py
│ │ └── user_service.py
├── MongoApp
│ ├── __init__.py
│ ├── main.py
│ └── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ ├── config.py
│ │ ├── example
│ │ ├── __init__.py
│ │ ├── example_controller.py
│ │ ├── example_entity.py
│ │ ├── example_model.py
│ │ ├── example_module.py
│ │ └── example_service.py
│ │ ├── product
│ │ ├── __init__.py
│ │ ├── product_controller.py
│ │ ├── product_entity.py
│ │ ├── product_model.py
│ │ ├── product_module.py
│ │ └── product_service.py
│ │ └── user
│ │ ├── __init__.py
│ │ ├── user_controller.py
│ │ ├── user_entity.py
│ │ ├── user_model.py
│ │ ├── user_module.py
│ │ └── user_service.py
├── OrmAsyncApp
│ ├── __init__.py
│ ├── main.py
│ └── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ ├── config.py
│ │ ├── example
│ │ ├── __init__.py
│ │ ├── example_controller.py
│ │ ├── example_entity.py
│ │ ├── example_model.py
│ │ ├── example_module.py
│ │ └── example_service.py
│ │ ├── product
│ │ ├── __init__.py
│ │ ├── product_controller.py
│ │ ├── product_entity.py
│ │ ├── product_model.py
│ │ ├── product_module.py
│ │ └── product_service.py
│ │ └── user
│ │ ├── __init__.py
│ │ ├── user_controller.py
│ │ ├── user_entity.py
│ │ ├── user_model.py
│ │ ├── user_module.py
│ │ └── user_service.py
├── OrmSyncApp
│ ├── __init__.py
│ ├── main.py
│ └── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ ├── config.py
│ │ ├── example
│ │ ├── __init__.py
│ │ ├── example_controller.py
│ │ ├── example_entity.py
│ │ ├── example_model.py
│ │ ├── example_module.py
│ │ └── example_service.py
│ │ ├── product
│ │ ├── __init__.py
│ │ ├── product_controller.py
│ │ ├── product_entity.py
│ │ ├── product_model.py
│ │ ├── product_module.py
│ │ └── product_service.py
│ │ └── user
│ │ ├── __init__.py
│ │ ├── user_controller.py
│ │ ├── user_entity.py
│ │ ├── user_model.py
│ │ ├── user_module.py
│ │ └── user_service.py
├── __init__.py
└── guard_examples.py
├── mkdocs.yml
├── nest
├── __init__.py
├── cli
│ ├── __init__.py
│ ├── cli.py
│ ├── click_handlers.py
│ ├── src
│ │ ├── __init__.py
│ │ ├── app_controller.py
│ │ ├── app_module.py
│ │ ├── app_service.py
│ │ └── generate
│ │ │ ├── __init__.py
│ │ │ ├── generate_controller.py
│ │ │ ├── generate_model.py
│ │ │ ├── generate_module.py
│ │ │ └── generate_service.py
│ └── templates
│ │ ├── __init__.py
│ │ ├── abstract_empty_template.py
│ │ ├── base_template.py
│ │ ├── blank_template.py
│ │ ├── cli_templates.py
│ │ ├── mongo_db_template.py
│ │ ├── mongo_template.py
│ │ ├── mysql_template.py
│ │ ├── orm_template.py
│ │ ├── postgres_template.py
│ │ ├── relational_db_template.py
│ │ ├── sqlite_template.py
│ │ └── templates_factory.py
├── common
│ ├── __init__.py
│ ├── constants.py
│ ├── exceptions.py
│ ├── module.py
│ └── route_resolver.py
└── core
│ ├── __init__.py
│ ├── cli_factory.py
│ ├── database
│ ├── __init__.py
│ ├── base_config.py
│ ├── odm_config.py
│ ├── odm_provider.py
│ ├── orm_config.py
│ └── orm_provider.py
│ ├── decorators
│ ├── __init__.py
│ ├── class_based_view.py
│ ├── cli
│ │ ├── __init__.py
│ │ └── cli_decorators.py
│ ├── controller.py
│ ├── database.py
│ ├── guards.py
│ ├── http_code.py
│ ├── http_method.py
│ ├── injectable.py
│ ├── module.py
│ └── utils.py
│ ├── pynest_app_context.py
│ ├── pynest_application.py
│ ├── pynest_container.py
│ └── pynest_factory.py
├── pyproject.toml
└── tests
├── __init__.py
├── test_common
├── __init__.py
├── test_module.py
└── test_route_resolver.py
└── test_core
├── __init__.py
├── test_app_context.py
├── test_database
├── __init__.py
├── test_odm.py
└── test_orm.py
├── test_decorators
├── __init__.py
├── test_controller.py
└── test_guard.py
├── test_pynest_application.py
├── test_pynest_container.py
└── test_pynest_factory.py
/.github/workflows/cli_test.yaml:
--------------------------------------------------------------------------------
1 | name: CLI Test
2 |
3 | on: [push, workflow_call, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | app_type: ["Blank", "SyncORM", "AsyncORM", "MongoDB", "PostgresSync", "PostgresAsync", "MySQLSync", "MySQLAsync"]
12 |
13 | steps:
14 | - name: Check out repository code
15 | uses: actions/checkout@v3
16 |
17 | - name: Set up Python
18 | uses: actions/setup-python@v4
19 | with:
20 | python-version: '3.9'
21 |
22 | - name: Install dependencies
23 | run: |
24 | pip install poetry
25 | poetry install
26 |
27 | - name: Test CLI Commands
28 | run: |
29 | app_name="${{ matrix.app_type }}App"
30 | case "${{ matrix.app_type }}" in
31 | "Blank")
32 | poetry run pynest generate application -n "$app_name"
33 | ;;
34 | "SyncORM")
35 | poetry run pynest generate application -n "$app_name" -db sqlite
36 | ;;
37 | "AsyncORM")
38 | poetry run pynest generate application -n "$app_name" -db sqlite --is-async
39 | ;;
40 | "MongoDB")
41 | poetry run pynest generate application -n "$app_name" -db mongodb
42 | ;;
43 | "PostgresSync")
44 | poetry run pynest generate application -n "$app_name" -db postgresql
45 | ;;
46 | "PostgresAsync")
47 | poetry run pynest generate application -n "$app_name" -db postgresql --is-async
48 | ;;
49 | "MySQLSync")
50 | poetry run pynest generate application -n "$app_name" -db mysql
51 | ;;
52 | "MySQLAsync")
53 | poetry run pynest generate application -n "$app_name" -db mysql --is-async
54 | ;;
55 | esac
56 |
57 | cd "$app_name"
58 | poetry run pynest generate resource -n user
59 |
60 | - name: Verify Boilerplate
61 | run: |
62 | app_name="${{ matrix.app_type }}App"
63 | if [ -d "$app_name" ]; then
64 | echo "Directory $app_name exists."
65 | else
66 | echo "Directory $app_name does not exist."
67 | exit 1
68 | fi
69 |
70 | if [ -d "$app_name/src/user" ]; then
71 | echo "Directory $app_name/src/user exists."
72 | else
73 | echo "Directory $app_name does not exist."
74 | exit 1
75 | fi
76 |
77 | # List of expected files
78 | declare -a files=("main.py" "requirements.txt" "README.md")
79 | declare -a src_level_files=("app_module.py" "app_service.py" "app_controller.py")
80 | declare -a module_files=("user_controller.py" "user_service.py" "user_module.py" "user_model.py")
81 |
82 | # Check each file in the list of files
83 | for file in "${files[@]}"; do
84 | if [ -f "$app_name/$file" ]; then
85 | echo "$file exists in $app_name."
86 | else
87 | echo "$file does not exist in $app_name."
88 | exit 1
89 | fi
90 | done
91 |
92 | # Check each file in the list of files
93 | for file in "${src_level_files[@]}"; do
94 | if [ -f "$app_name/src/$file" ]; then
95 | echo "$file exists in $app_name."
96 | else
97 | echo "$file does not exist in $app_name."
98 | exit 1
99 | fi
100 | done
101 |
102 | # Check each file in the list of module_files
103 | for file in "${module_files[@]}"; do
104 | if [ -f "$app_name/src/user/$file" ]; then
105 | echo "$file exists in $app_name."
106 | else
107 | echo "$file does not exist in $app_name."
108 | exit 1
109 | fi
110 | done
111 |
112 | echo "Boilerplate for ${{ matrix.app_type }} generated successfully."
--------------------------------------------------------------------------------
/.github/workflows/deploy_docs.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy Docs
2 |
3 | on: [workflow_call, workflow_dispatch]
4 |
5 | permissions:
6 | contents: read
7 | pages: write
8 | id-token: write
9 |
10 | concurrency:
11 | group: "pages"
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | deploy_docs:
16 | environment:
17 | name: github-pages
18 | url: ${{ steps.deployment.outputs.page_url }}
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v3
24 |
25 | - name: Copy License File
26 | run: cp LICENSE docs/license.md
27 |
28 | - name: Set up Python
29 | uses: actions/setup-python@v4
30 | with:
31 | python-version: "3.9"
32 |
33 | - name: Install dependencies
34 | run: |
35 | pip install poetry
36 | poetry install --with docs
37 |
38 | - name: Build docs
39 | run: poetry run mkdocs build --clean
40 |
41 | - name: Setup Pages
42 | uses: actions/configure-pages@v3
43 |
44 | - name: Upload artifact
45 | uses: actions/upload-pages-artifact@v1
46 | with:
47 | path: 'site'
48 |
49 | - name: Deploy to GitHub Pages
50 | id: deployment
51 | uses: actions/deploy-pages@v1
52 |
--------------------------------------------------------------------------------
/.github/workflows/integration_test.yaml:
--------------------------------------------------------------------------------
1 | name: Integration Test
2 |
3 | on: [push, workflow_call, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | app_type: ["Blank", "SyncORM", "AsyncORM"]
12 |
13 | steps:
14 | - name: Check out repository code
15 | uses: actions/checkout@v3
16 |
17 | - name: Set up Python
18 | uses: actions/setup-python@v4
19 | with:
20 | python-version: '3.9'
21 |
22 | - name: Install dependencies
23 | run: |
24 | pip install poetry
25 | poetry install
26 |
27 | - name: Start Application
28 | run: |
29 | if [ "${{ matrix.app_type }}" == "Blank" ]; then
30 | app_name="NestApp"
31 | is_async=""
32 | elif [ "${{ matrix.app_type }}" == "SyncORM" ]; then
33 | app_name="ORMNestApp"
34 | is_async=""
35 | elif [ "${{ matrix.app_type }}" == "AsyncORM" ]; then
36 | app_name="AsyncORMNestApp"
37 | is_async="--is-async"
38 | fi
39 |
40 | if [ "${{ matrix.app_type }}" == "Blank" ]; then
41 | poetry run pynest generate application -n "$app_name"
42 | else
43 | poetry run pynest generate application -n "$app_name" -db sqlite $is_async
44 | poetry add aiosqlite
45 | fi
46 |
47 | cd "$app_name"
48 | poetry run pynest generate resource -n user
49 | poetry run uvicorn "src.app_module:http_server" --host "0.0.0.0" --port 8000 --reload &
50 |
51 | - name: Wait for the server to start
52 | run: sleep 10
53 |
54 | - name: Test the application
55 | run: |
56 | curl -f http://localhost:8000/docs
57 | curl -f -X 'POST' \
58 | "http://localhost:8000/user/" \
59 | -H 'accept: application/json' \
60 | -H 'Content-Type: application/json' \
61 | -d '{"name": "Example Name"}'
62 | curl -f http://localhost:8000/user/
63 |
64 | - name: Kill the server
65 | run: kill $(jobs -p) || true
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release Package
2 |
3 | on:
4 | workflow_dispatch:
5 | permissions:
6 | users:
7 | - ItayTheDar
8 | inputs:
9 | increment_version:
10 | description: 'Increment version by major, minor, or patch'
11 | required: true
12 | default: 'patch'
13 | type: choice
14 | options:
15 | - major
16 | - minor
17 | - patch
18 |
19 | env:
20 | VERSION_FILE_PATH: pyproject.toml
21 | CHANGELOG_FILE_PATH: CHANGELOG.md
22 |
23 | jobs:
24 | run-tests:
25 | uses: ./.github/workflows/tests.yaml
26 |
27 | release-package:
28 | runs-on: ubuntu-latest
29 | needs: run-tests
30 | permissions:
31 | contents: write
32 |
33 | steps:
34 | - name: Checkout repository
35 | uses: actions/checkout@v3
36 | with:
37 | token: ${{ secrets.RELEASE_GIT_TOKEN }}
38 |
39 | - name: Install Python
40 | uses: actions/setup-python@v4
41 | with:
42 | python-version: "3.10"
43 |
44 | - name: Install Poetry
45 | run: |
46 | pip install poetry
47 |
48 | - name: Set version using Poetry
49 | run: |
50 | # Extract the current version
51 | CURRENT_VERSION=$(poetry version -s)
52 | echo "Current version: $CURRENT_VERSION"
53 |
54 | # Increment version
55 | case ${{ inputs.increment_version }} in
56 | major)
57 | NEW_VERSION=$(poetry version major | awk '{print $NF}')
58 | ;;
59 | minor)
60 | NEW_VERSION=$(poetry version minor | awk '{print $NF}')
61 | ;;
62 | patch)
63 | NEW_VERSION=$(poetry version patch | awk '{print $NF}')
64 | ;;
65 | *)
66 | echo "Invalid input for increment_version"
67 | exit 1
68 | ;;
69 | esac
70 | echo "New version: $NEW_VERSION"
71 | echo "RELEASE_VERSION=$NEW_VERSION" >> $GITHUB_ENV
72 |
73 | - name: Install build dependencies
74 | run: |
75 | poetry install --with build
76 |
77 | - name: Update CHANGELOG.md
78 | run: poetry run git-changelog . -o $CHANGELOG_FILE_PATH
79 |
80 | - name: Build package with Poetry
81 | run: |
82 | poetry build
83 |
84 | - name: Commit and push changes
85 | run: |
86 | git config --global user.name "github-actions"
87 | git config --global user.email "github@actions.com"
88 | git add $CHANGELOG_FILE_PATH pyproject.toml
89 | git commit -m "Increment version to $NEW_VERSION"
90 | git push
91 |
92 | - name: Publish package to PyPI
93 | run: |
94 | poetry publish --username ${{ secrets.PYPI_API_USER }} --password ${{ secrets.PYPI_API_TOKEN }}
95 |
96 | - name: Create GitHub release
97 | uses: softprops/action-gh-release@v1
98 | with:
99 | name: v${{ env.RELEASE_VERSION }}
100 | tag_name: v${{ env.RELEASE_VERSION }}
101 |
102 | deploy-docs:
103 | needs: release-package
104 | permissions:
105 | contents: read
106 | pages: write
107 | id-token: write
108 | uses: ./.github/workflows/deploy_docs.yaml
109 |
--------------------------------------------------------------------------------
/.github/workflows/release_beta.yaml:
--------------------------------------------------------------------------------
1 | name: Release Beta
2 |
3 | on:
4 | workflow_dispatch:
5 | permissions:
6 | users:
7 | - ItayTheDar
8 | inputs:
9 | increment_version:
10 | description: 'Increment version by major, minor, or patch'
11 | required: true
12 | default: 'patch'
13 | type: choice
14 | options:
15 | - major
16 | - minor
17 | - patch
18 |
19 |
20 | env:
21 | VERSION_FILE_PATH: nest/__init__.py
22 | CHANGELOG_FILE_PATH: CHANGELOG.md
23 |
24 | jobs:
25 | run-tests:
26 | uses: ./.github/workflows/tests.yaml
27 |
28 | release-beta:
29 | runs-on: ubuntu-latest
30 | needs: run-tests
31 | permissions:
32 | contents: write
33 |
34 | steps:
35 | - name: Checkout repository
36 | uses: actions/checkout@v3
37 | with:
38 | token: ${{ secrets.RELEASE_GIT_TOKEN }}
39 |
40 | - name: Set version
41 | run: |
42 | VERSION_REGEX='^(__version__ = \")(([[:digit:]]+\.)*[[:digit:]]+)((a|b|rc)[[:digit:]]+)?(\.post[[:digit:]]+)?(.dev[[:digit:]]+)?(\")$'
43 |
44 | # get current version from version file
45 | CURRENT_VERSION=`sed -n -E "s/$VERSION_REGEX/\2/p" $VERSION_FILE_PATH`
46 | echo "Current version: $CURRENT_VERSION"
47 |
48 | # switch case for incrementing_version based on input
49 | case ${{ inputs.increment_version }} in
50 | major)
51 | NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$1=$1+1; $2=0; $3=0; print $0}' OFS=".")
52 | ;;
53 | minor)
54 | NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$2=$2+1; $3=0; print $0}' OFS=".")
55 | ;;
56 | patch)
57 | NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{$3=$3+1; print $0}' OFS=".")
58 | ;;
59 | *)
60 | echo "Invalid input for increment_version"
61 | exit 1
62 | ;;
63 | esac
64 | echo "New version: $NEW_VERSION"
65 | echo "RELEASE_VERSION=$NEW_VERSION" >> $GITHUB_ENV
66 |
67 | # update version file
68 | sed -i -E "s/$VERSION_REGEX/\1$NEW_VERSION\8/" $VERSION_FILE_PATH
69 |
70 |
71 | - name: Install python
72 | uses: actions/setup-python@v4
73 | with:
74 | python-version: "3.10"
75 |
76 | - name: Install python dependencies
77 | run: |
78 | pip install --upgrade pip
79 | pip install -r requirements-release.txt
80 |
81 | - name: Update CHANGELOG.md
82 | run: git-changelog . -o $CHANGELOG_FILE_PATH
83 |
84 |
85 | - name: Build package
86 | run: python -m build
87 |
88 | - name: Publish package to TestPyPI
89 | run: |
90 | python -m twine upload dist/* -r testpypi -u ${{ secrets.TEST_PYPI_API_USER }} -p ${{ secrets.TEST_PYPI_API_TOKEN }}
91 |
92 | - name: Install test package
93 | run: |
94 | pip install --index-url https://test.pypi.org/simple/ --no-deps pynest-api
95 |
96 | # import to check if package is installed correctly
97 | python -c "import nest"
98 |
99 | - name: Copy pip url
100 | run: |
101 | echo "pip install --index-url https://test.pypi.org/simple/ --no-deps pynest-api"
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push, workflow_call, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | python-version: ["3.9", "3.10", "3.11"]
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Install Python
16 | uses: actions/setup-python@v4
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 |
20 | - name: Install dependencies
21 | run: |
22 | pip install poetry
23 | poetry install --with test
24 |
25 | - name: Run tests
26 | run: |
27 | poetry run pytest tests
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python-specific
2 | __pycache__/
3 | *.pyc
4 | *.pyo
5 | *.pyd
6 |
7 | # Dependency directories
8 | venv/
9 | env/
10 | envs/
11 | .env
12 | poetry.lock
13 | requirements.txt
14 |
15 | # Compiled source
16 | *.com
17 | *.class
18 | *.dll
19 | *.exe
20 | *.o
21 | *.so
22 |
23 | # Logs and databases
24 | *.log
25 | *.sqlite
26 | *.db
27 |
28 | # Development and IDE files
29 | .vscode/
30 | .idea/
31 | *.swp
32 | *~
33 |
34 | # Compiled files
35 | dist/
36 | build/
37 | *.egg-info/
38 |
39 | # Testing
40 | htmlcov/
41 | .coverage
42 | .tox/
43 |
44 | # Miscellaneous
45 | .DS_Store
46 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 |
9 | ## Unreleased
10 |
11 | [Compare with latest](https://github.com/PythonNest/PyNest/compare/44cfa3388619453b0fac619802060ebafb3f4074...HEAD)
12 |
13 | ### Added
14 |
15 | - Add guard support (#107) ([44cfa33](https://github.com/PythonNest/PyNest/commit/44cfa3388619453b0fac619802060ebafb3f4074) by Itay Dar).
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 PyNest
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.
--------------------------------------------------------------------------------
/docs/blank.md:
--------------------------------------------------------------------------------
1 | # Blank Application with PyNest
2 |
3 | ## Introduction
4 |
5 | This documentation introduces a creation of the simplest Pynest Application.
6 |
7 | ### Requirements
8 |
9 | - Python 3.9+
10 | - PyNest (latest version)
11 |
12 | ## Setting Up
13 |
14 | ### Installation and Setup
15 |
16 | Ensure you have the latest version of PyNest installed. You can install them using pip:
17 |
18 | ```bash
19 | pip install pynest-api
20 | ```
21 |
22 | ## Start with cli
23 |
24 | #### Create a new project
25 |
26 | ```bash
27 | pynest create-nest-app -n my_app_name
28 | ```
29 |
30 | this command will create a new project with the following structure:
31 |
32 | ```text
33 | ├── app.py
34 | ├── main.py
35 | |── requirements.txt
36 | |── README.md
37 | ├── src
38 | │ ├── __init__.py
39 | │ ├── app_module.py
40 | ├── |── app_controller.py
41 | ├── |── app_service.py
42 | ```
43 |
44 | After creating the project, let's create a new module:
45 |
46 | ```bash
47 | pynest g module -n examples
48 | ```
49 |
50 | This will create a new module called examples in your application with the following structure under the src folder:
51 |
52 | ```text
53 | ├── examples
54 | │ ├── __init__.py
55 | │ ├── examples_controller.py
56 | │ ├── examples_service.py
57 | │ ├── examples_model.py
58 | │ ├── examples_module.py
59 | ```
60 |
61 | Let's go over the boilerplate code the cli generated:
62 |
63 | `app_service.py`
64 | ```python
65 | from nest.core import Injectable
66 |
67 |
68 | @Injectable
69 | class AppService:
70 | def __init__(self):
71 | self.app_name = "BlankApp"
72 | self.app_version = "1.0.0"
73 |
74 | async def get_app_info(self):
75 | return {"app_name": self.app_name, "app_version": self.app_version}
76 | ```
77 |
78 | `app_controller.py`
79 | ```python
80 | from nest.core import Controller, Get
81 |
82 | from .app_service import AppService
83 |
84 |
85 | @Controller("/")
86 | class AppController:
87 |
88 | def __init__(self, service: AppService):
89 | self.service = service
90 |
91 | @Get("/")
92 | async def get_app_info(self):
93 | return await self.service.get_app_info()
94 | ```
95 |
96 | `app_module.py`
97 |
98 | ```python
99 | from nest.core import PyNestFactory, Module
100 | from src.example.example_module import ExampleModule
101 | from .app_controller import AppController
102 | from .app_service import AppService
103 | from fastapi import FastAPI
104 |
105 |
106 | @Module(
107 | controllers=[AppController],
108 | providers=[AppService],
109 | imports=[ExampleModule],
110 | )
111 | class AppModule:
112 | pass
113 |
114 |
115 | app = PyNestFactory.create(AppModule, description="This is my FastAPI app", title="My App", version="1.0.0", debug=True)
116 |
117 | http_server: FastAPI = app.get_server()
118 | ```
119 |
120 | `@Module(...)`: This is a decorator that defines a module. In PyNest, a module is a class annotated with a `@Module()` decorator.
121 | The imports array includes the modules required by this module. In this case, ExampleModule is imported. The controllers and providers arrays are empty here, indicating this module doesn't directly provide any controllers or services.
122 |
123 | `PyNestFactory.create()` is a command to create an instance of the application.
124 | The AppModule is passed as an argument, which acts as the root module of the application.
125 | Additional metadata like description, title, version, and debug flag are also provided
126 |
127 | `http_server: FastAPI = app.get_server()`: Retrieves the HTTP server instance from the application.
128 |
129 | ### Creating Model
130 |
131 | Create a model to represent the data that will be stored in the database.
132 |
133 | `examples_model.py`
134 |
135 | ```python
136 | from pydantic import BaseModel
137 |
138 |
139 | class Examples(BaseModel):
140 | name: str
141 | ```
142 |
143 | ### Creating Service
144 |
145 | Implement services to handle business logic.
146 |
147 | `examples_service.py`
148 |
149 | ```python
150 | from .examples_model import Examples
151 | from nest.core import Injectable
152 |
153 |
154 | @Injectable
155 | class ExamplesService:
156 | def __init__(self):
157 | self.database = []
158 |
159 | async def get_examples(self):
160 | return self.database
161 |
162 | async def add_examples(self, examples: Examples):
163 | self.database.append(examples)
164 | return {"message": "Example added successfully"}
165 |
166 | ```
167 |
168 | create a controller to handle the requests and responses. The controller should call the service to execute business
169 | logic.
170 |
171 | `example_controller.py`
172 |
173 | ```python
174 | from nest.core import Controller, Get, Post
175 |
176 | from .examples_service import ExamplesService
177 | from .examples_model import Examples
178 |
179 |
180 | @Controller("examples")
181 | class ExamplesController:
182 |
183 | def __init__(self, service: ExamplesService):
184 | self.service = service
185 |
186 | @Get("/")
187 | async def get_examples(self):
188 | return await self.service.get_examples()
189 |
190 | @Post("/")
191 | async def add_examples(self, examples: Examples):
192 | return await self.service.add_examples(examples)
193 | ```
194 |
195 | ## Creating Module
196 |
197 | create the module file to register the controller and the service
198 |
199 | `examples_module.py`
200 |
201 | ```python
202 | from nest.core import Module
203 | from .examples_controller import ExamplesController
204 | from .examples_service import ExamplesService
205 |
206 |
207 | @Module(
208 | controllers=[ExamplesController],
209 | providers=[ExamplesService],
210 | )
211 | class ExamplesModule:
212 | pass
213 | ```
214 |
215 | ## Run the application
216 |
217 | ```shell
218 | uvicorn "src.app_module:http_server" --host "0.0.0.0" --port "8000" --reload
219 | ```
220 |
221 | Now you can access the application at http://localhost:8000/docs and test the endpoints.
222 |
223 | ---
224 |
225 |
233 |
--------------------------------------------------------------------------------
/docs/book_resource_api_docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/book_resource_api_docs.png
--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------
1 | # PyNest CLI Deep Dive 🔍
2 |
3 | The PyNest CLI is a powerful tool that helps you quickly scaffold, develop, and manage your PyNest applications. This guide will walk you through all the commands and best practices to make the most out of the PyNest CLI.
4 |
5 | ## Installation 💻
6 |
7 | Before using the PyNest CLI, ensure you have it installed. You can install it using pip:
8 |
9 | ```bash
10 | pip install pynest-api
11 | ```
12 |
13 | ## CLI Commands and Usage 🛠️
14 | The PyNest CLI provides a variety of commands to help you create and manage your PyNest applications. Below are the detailed descriptions of the available commands and their usage.
15 |
16 | ### Generate New Application
17 | Create a new PyNest application.
18 |
19 | ```bash
20 | pynest generate application --app-name [--db-type ] [--is-async]
21 | ```
22 | **Options**
23 |
24 | * `--app-name`, `-n`: The name of the new application. (Required)
25 | * `--db-type`, `-db`: The type of database to use (postgresql, mysql, sqlite, mongodb). (Optional)
26 | * `--is-async`: Whether the project should be async or not. This is applicable only for relational databases. (Optional)
27 | * `--is-cli`: Whether the project should be a CLI App. (Optional)
28 |
29 | #### Example
30 | ```bash
31 | pynest generate application --app-name my_pynest_app --db-type postgresql --is-async
32 | ```
33 |
34 | This example will create a skeleton PyNest application named `my_pynest_app` with PostgreSQL as the database and async support.
35 |
36 | #### File Structure 🗂️
37 | Here's the typical file structure of a PyNest application generated by the CLI:
38 |
39 | ```text
40 | my_pynest_app/
41 | ├── src/
42 | │ ├── __init__.py
43 | │ ├── app_module.py
44 | │ ├── app_controller.py
45 | │ ├── app_service.py
46 | │ ├── config.py
47 | ├── main.py
48 | ├── requirements.txt
49 | └── README.md
50 | ```
51 |
52 | !Note: The actual file structure may vary based on the modules and components you create
53 |
54 |
55 | ### generate command
56 | Generate boilerplate code for various components of the application.
57 |
58 | #### Subcommands
59 |
60 | **Resource**
61 | Generate a new resource with the associated controller, service, model and module files, with respect to the project configurations (e.g. database type).
62 |
63 | ```bash
64 | pynest generate resource --name
65 | ```
66 |
67 | **Options**
68 |
69 |
70 | * `--name`, `-n`: The name of the new module. (Required)
71 | * `--path`, `-p`: The path where the module should be created. (Optional)
72 |
73 | **Example**
74 | ```bash
75 | pynest generate resource --name users
76 | ```
77 |
78 | This will create a new resource named `users` with the associated controller, service, model and module files.
79 |
80 |
81 | **Module**
82 |
83 | Generate a new module file, which can be used to group related components of the application.
84 |
85 | ```bash
86 | pynest generate module --name
87 | ```
88 |
89 | **Options**
90 |
91 | * `--name`, `-n`: The name of the new module. (Required)
92 | * `--path`, `-p`: The path where the module should be created. (Optional)
93 |
94 | **Example**
95 | ```bash
96 | pynest generate module --name auth
97 | ```
98 |
99 | This will create a new module named `auth` in the default path.
100 |
101 | **Controller**
102 |
103 | Generate a new controller file, which will handle the routing and responses for a specific resource.
104 |
105 | ```bash
106 | pynest generate controller --name
107 | ```
108 |
109 | **Options**
110 |
111 | * `--name`, `-n`: The name of the new controller. (Required)
112 | * `--path`, `-p`: The path where the controller should be created. (Optional)
113 |
114 | **Example**
115 | ```bash
116 | pynest generate controller --name users
117 | ```
118 |
119 | This will create a new controller named `users` in the default path.
120 |
121 | **Service**
122 |
123 | Generate a new service file, which will contain the business logic for a specific resource.
124 |
125 | ```bash
126 | pynest generate service --name
127 | ```
128 |
129 | **Options**
130 |
131 | * `--name`, `-n`: The name of the new service. (Required)
132 | * `--path`, `-p`: The path where the service should be created. (Optional)
133 |
134 | **Example**
135 | ```bash
136 | pynest generate service --name users
137 | ```
138 |
139 | This will create a new service named `users` in the default path.
140 |
141 |
142 | ## Best Practices 🌟
143 |
144 | To ensure a clean and maintainable codebase, follow these best practices when using the PyNest CLI:
145 |
146 | * **Consistent Naming Conventions**: Use consistent naming conventions for files and directories. For example, use lowercase with underscores for module names (users_module.py) and camelCase for class names (UsersController)._
147 |
148 | * **Modular Structure**: Keep your code modular. Each module should encapsulate a specific functionality of your application.
149 |
150 | * **Service Layer**: Keep business logic within services to maintain separation of concerns. Controllers should handle HTTP requests and responses only.
151 |
152 | * **Dependency Injection**: Use dependency injection to manage dependencies between services and controllers. This makes your code more testable and maintainable.
153 |
154 | * **Environment Configuration**: Use environment variables to manage configuration settings, such as database connection strings and API keys.
155 |
156 | The PyNest CLI is a powerful tool that simplifies the development of PyNest applications. By following the best practices and utilizing the commands effectively, you can build scalable and maintainable applications with ease. Happy coding!
157 |
158 | ---
159 |
--------------------------------------------------------------------------------
/docs/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | This guide will help you get started with setting up a new PyNest project, creating the essential modules, and running your application.
4 |
5 | ## Installation
6 |
7 | To install PyNest, ensure you have Python 3.9+ installed. Then, install PyNest using pip:
8 |
9 | ```bash
10 | pip install pynest-api
11 | ```
12 |
13 | ## Creating a New Project 📂
14 |
15 | Start by creating a new directory for your project and navigating into it:
16 |
17 | ```bash
18 | mkdir my_pynest_project
19 | cd my_pynest_project
20 | ```
21 |
22 | ## Creating the Application Structure 🏗️
23 |
24 | We'll create the basic structure of a PyNest application. Here's what it will look like:
25 |
26 | ```text
27 | my_pynest_project/
28 | ├── src/
29 | │ ├── __init__.py
30 | │ ├── app_module.py
31 | │ ├── app_controller.py
32 | │ ├── app_service.py
33 | ├── main.py
34 | ├── requirements.txt
35 | └── README.md
36 | ```
37 |
38 | ### Step 1: Create app_module.py
39 |
40 | The app_module.py file is where we define our main application module. Create the file and add the following code:
41 |
42 | ```python
43 | # src/app_module.py
44 |
45 | from nest.core import Module, PyNestFactory
46 | from .app_controller import AppController
47 | from .app_service import AppService
48 |
49 | @Module(
50 | controllers=[AppController],
51 | providers=[AppService],
52 | )
53 | class AppModule:
54 | pass
55 |
56 | app = PyNestFactory.create(
57 | AppModule,
58 | description="This is my PyNest app",
59 | title="My App",
60 | version="1.0.0",
61 | debug=True,
62 | )
63 |
64 | http_server = app.get_server()
65 | ```
66 |
67 | ### Step 2: Create app_service.py
68 |
69 | The app_service.py file will contain the logic for our service. Create the file and add the following code:
70 |
71 | ```python
72 | # src/app_service.py
73 |
74 | from nest.core import Injectable
75 |
76 | @Injectable
77 | class AppService:
78 | def __init__(self):
79 | self.app_name = "MyApp"
80 | self.app_version = "1.0.0"
81 |
82 | def get_app_info(self):
83 | return {"app_name": self.app_name, "app_version": self.app_version}
84 | ```
85 |
86 | ### Step 3: Create app_controller.py
87 |
88 | The app_controller.py file will handle the routing and responses. Create the file and add the following code:
89 |
90 | ```python
91 | # src/app_controller.py
92 |
93 | from nest.core import Controller, Get
94 | from .app_service import AppService
95 |
96 | @Controller("/")
97 | class AppController:
98 | def __init__(self, service: AppService):
99 | self.service = service
100 |
101 | @Get("/")
102 | def get_app_info(self):
103 | return self.service.get_app_info()
104 | ```
105 |
106 | ### Step 4: Create main.py
107 |
108 | The main.py file will run our PyNest application. Create the file and add the following code:
109 |
110 | ```python
111 | # main.py
112 |
113 | import uvicorn
114 | from src.app_module import http_server
115 |
116 | if __name__ == "__main__":
117 | uvicorn.run(http_server, host="0.0.0.0", port=8000, reload=True)
118 | ```
119 |
120 | ### File Structure 🗂️
121 |
122 | Here's the file structure of your PyNest application after following the steps:
123 |
124 | ```text
125 | my_pynest_project/
126 | ├── src/
127 | │ ├── __init__.py
128 | │ ├── app_module.py
129 | │ ├── app_controller.py
130 | │ ├── app_service.py
131 | ├── main.py
132 | ├── requirements.txt
133 | └── README.md
134 | ```
135 |
136 | ### Running the Application ▶️
137 |
138 | To run your application, execute the following command:
139 |
140 | ```bash
141 | python main.py
142 | ```
143 |
144 | You should see the Uvicorn server starting, and you can access your API at .
145 |
146 | ---
147 |
--------------------------------------------------------------------------------
/docs/imgs/DI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/imgs/DI.png
--------------------------------------------------------------------------------
/docs/imgs/module-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/imgs/module-architecture.png
--------------------------------------------------------------------------------
/docs/imgs/pynest-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/imgs/pynest-logo.png
--------------------------------------------------------------------------------
/docs/imgs/pynest_new_logo_opt-removebg-preview.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/imgs/pynest_new_logo_opt-removebg-preview.jpeg
--------------------------------------------------------------------------------
/docs/imgs/pynest_new_logo_opt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/docs/imgs/pynest_new_logo_opt.png
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Overview 🚀
2 |
3 | Welcome to **PyNest**! PyNest is a Python framework built on top of FastAPI that follows the modular architecture of NestJS. It's designed to help you structure your APIs in an intuitive, easy-to-understand, and enjoyable way.
4 |
5 | With PyNest, you can build scalable and maintainable APIs with ease. The framework supports dependency injection, type annotations, decorators, and code generation, making it easy to write clean and testable code.
6 |
7 | This framework is not a direct port of NestJS to Python but rather a re-imagining of the framework specifically for Python developers, including data scientists, data analysts, and data engineers. It aims to assist them in building better and faster APIs for their data applications.
8 |
9 | ## Key Features ✨
10 |
11 | ### Modular Architecture 🏗️
12 |
13 | PyNest follows the modular architecture of NestJS, which allows for easy separation of concerns and code organization. Each module contains a collection of related controllers, services, and providers.
14 |
15 | ### Dependency Injection 💉
16 |
17 | PyNest supports dependency injection, which makes it easy to manage dependencies and write testable code. You can easily inject services and providers into your controllers using decorators.
18 |
19 | ### Decorators 🏷️
20 |
21 | PyNest makes extensive use of decorators to define routes, middleware, and other application components. This helps keep the code concise and easy to read.
22 |
23 | ### Type Annotations 📝
24 |
25 | PyNest leverages Python's type annotations to provide better tooling and help prevent errors. You can annotate your controllers, services, and providers with types to make your code more robust.
26 |
27 | ### Code Generation ⚙️
28 |
29 | PyNest includes a code generation tool that can create boilerplate code for modules, controllers, and other components. This saves you time and helps you focus on writing the code that matters.
30 |
31 | ## Contributing 🤝
32 |
33 | Contributions are welcome! Please feel free to submit a Pull Request.
34 |
35 | ## Credits 💡
36 |
37 | - [NestJS](https://nestjs.com/), a framework for building Node.js applications.
38 | - [FastAPI](https://fastapi.tiangolo.com/), a framework for building Python applications.
39 |
40 | ---
41 |
42 |
47 |
--------------------------------------------------------------------------------
/docs/license.md:
--------------------------------------------------------------------------------
1 | # License
2 |
3 | PyNest is [MIT licensed](https://github.com/PythonNest/PyNest/blob/main/LICENSE).
4 |
5 |
--------------------------------------------------------------------------------
/docs/lifespan_tasks.md:
--------------------------------------------------------------------------------
1 | # Lifaspan tasks in PyNest
2 |
3 | ## Introduction
4 |
5 | Lifespan tasks - coroutines, which run while app is working.
6 |
7 | ## Defining a lifespan task
8 | As example of lifespan task will use coroutine, which print time every hour. In real user cases can be everything else.
9 |
10 | ```python
11 | import asyncio
12 | from datetime import datetime
13 |
14 | async def print_current_time():
15 | while True:
16 | current_time = datetime.now().strftime("%H:%M:%S")
17 | print(f"Current time: {current_time}")
18 | await asyncio.sleep(3600)
19 | ```
20 |
21 | ## Implement a lifespan task
22 | In `app_module.py` we can define a startup handler, and run lifespan inside it
23 |
24 | ```python
25 | from nest.core import PyNestFactory
26 |
27 | app = PyNestFactory.create(
28 | AppModule,
29 | description="This is my PyNest app with lifespan task",
30 | title="My App",
31 | version="1.0.0",
32 | debug=True,
33 | )
34 |
35 | http_server = app.get_server()
36 |
37 | @http_server.on_event("startup")
38 | async def startup():
39 | await print_current_time()
40 | ```
41 |
42 | Now `print_current_time` will work in lifespan after startup.
43 |
--------------------------------------------------------------------------------
/docs/modules.md:
--------------------------------------------------------------------------------
1 | # Modules in PyNest 📦
2 |
3 | A module is a class annotated with a `@Module()` decorator. The `@Module()` decorator provides metadata that PyNest uses to organize the application structure.
4 |
5 | Each application has at least one module, a root module.
6 | The root module is the starting point PyNest uses
7 | to build the application-graph which is the internal data structure PyNest uses
8 | to resolve module and provider relationships and dependencies.
9 | While very small applications may theoretically have just the root module, this is not the typical case.
10 | Modules are strongly recommended as an effective way to organize your components.
11 | Thus, for most applications, the resulting architecture will employ multiple modules,
12 | each encapsulating a closely related set of capabilities.
13 |
14 | ## Defining a Module
15 |
16 | The `@Module()` decorator takes a single object whose properties describe the module:
17 |
18 | - **providers**: The providers that will be instantiated by the PyNest injector and that may be shared across this module.
19 | - **controllers**: The set of controllers defined in this module which have to be instantiated.
20 | - **imports**: The list of imported modules that export the providers which are required in this module.
21 | - **exports**: The subset of providers that are provided by this module and should be available in other modules that import this module. You can use either the provider itself or just its token (provide value).
22 |
23 | ---
24 |
25 | 
26 | > This diagram shows the relationship between modules, providers, and controllers in a PyNest application.
27 |
28 | ---
29 |
30 | **Code Example**
31 |
32 | ```python
33 | from pynest.core import Module
34 | from .book_controller import BookController
35 | from .book_service import BookService
36 |
37 | @Module(
38 | providers=[BookService],
39 | controllers=[BookController],
40 | exports=[BookService],
41 | )
42 | class BookModule:
43 | pass
44 | ```
45 |
46 | In the example above, we define a `BookModule` module that exports the `BookService`.
47 | This means that all the routes in the controllers will be registered in the application,
48 | and the `BookService` will be available for injection in other modules that import the `BookModule`.
49 |
50 | ## Module Interactions
51 | Modules interact with each other primarily through their exports and imports.
52 | A module can use a provider from another module if the provider is exported by the first module
53 | and the second module imports it.
54 |
55 | ### Example
56 | ```python
57 | from pynest.core import Module
58 | from .book_module import BookModule
59 |
60 | @Module(
61 | imports=[BookModule],
62 | )
63 | class AppModule:
64 | pass
65 | ```
66 |
67 | In this example, the `AppModule` imports the `BookModule`,
68 | which means that all the providers exported by the `BookModule` are available in the
69 | `AppModule` and all the routes in `BookController` will be registered to the main application.
70 |
71 |
72 | ---
73 |
74 |
--------------------------------------------------------------------------------
/docs/providers.md:
--------------------------------------------------------------------------------
1 | # Providers and Injectables in PyNest 🏗️
2 |
3 | Providers, also known as services or injectables, are fundamental in PyNest applications. They handle business logic and other functionalities, acting as dependencies that can be injected into other components like controllers. This guide explains what providers are, how to use them, how to import and export them in modules, and how to inject them into controllers and other services, with various examples.
4 |
5 | ## What is a Provider?
6 |
7 | In PyNest, a provider is a class annotated with the `@Injectable` decorator. This decorator makes the class available for dependency injection, allowing it to be injected into other classes such as controllers and services.
8 |
9 | ## Defining a Provider
10 |
11 | To define a provider, use the `@Injectable` decorator. This makes the class available for dependency injection within the module or globally if exported.
12 |
13 | **Example: Logger Service**
14 |
15 | `logger_service.py`
16 |
17 | ```python
18 | from nest.core import Injectable
19 |
20 | @Injectable
21 | class LoggerService:
22 | def log(self, message: str):
23 | print(f"LOG: {message}")
24 | ```
25 |
26 | In this example, the `LoggerService` class is defined as a provider by using the `@Injectable` decorator. This class can now be injected into other classes within the same module or exported to be used in other modules.
27 |
28 | ## Using Providers in Modules
29 |
30 | [Modules](modules.md) are used to group related providers, controllers, and other modules.
31 | Providers can be imported and exported in modules to be used across the application.
32 |
33 | **Example: S3 Module**
34 | `s3_module.py`
35 |
36 | ```python
37 | from nest.core import Module
38 | from .s3_service import S3Service
39 | from src.providers.logger.logger_service import LoggerService
40 | from src.providers.logger.logger_module import LoggerModule
41 |
42 |
43 | @Module(
44 | providers=[S3Service, LoggerService],
45 | exports=[S3Service],
46 | imports=[LoggerModule],
47 | )
48 | class S3Module:
49 | pass
50 | ```
51 |
52 | In this example:
53 |
54 | The S3Module defines a module that provides the S3Service and exports it for use in other modules.
55 | It is also importing the LoggerModule to use the LoggerService.
56 |
57 | ## Injecting Providers into Controllers and Services
58 |
59 | Providers can be injected into controllers and other services using dependency injection. This is done by declaring the dependency in the constructor of the class where the provider is needed.
60 |
61 | **Example: S3 Service and Controller**
62 |
63 | `s3_service.py`
64 |
65 | ```python
66 | from nest.core import Injectable
67 | from src.providers.logger.logger_service import LoggerService
68 |
69 | @Injectable
70 | class S3Service:
71 |
72 | def __init__(self, logger_service: LoggerService):
73 | self.logger_service = logger_service
74 |
75 | def upload_file(self, file_name: str):
76 | print(f"Uploading {file_name}")
77 | self.logger_service.log(f"Uploaded file: {file_name}")
78 | ```
79 |
80 | In this Service, the `LoggerService` is injected into the `S3Service` via the constructor.
81 |
82 | `s3_controller.py`
83 |
84 | ```python
85 | from nest.core import Controller, Post
86 | from .s3_service import S3Service
87 |
88 | @Controller('/s3')
89 | class S3Controller:
90 | def __init__(self, s3_service: S3Service):
91 | self.s3_service = s3_service
92 |
93 | @Post('/upload')
94 | def upload_file(self, file_name: str):
95 | self.s3_service.upload_file(file_name)
96 | return {"message": "File uploaded successfully!"}
97 | ```
98 |
99 | In these examples:
100 |
101 | The S3Service is injected into the S3Controller via the constructor.
102 |
103 | ## Importing and Exporting Providers
104 | Providers can be shared between modules by importing and exporting them.
105 |
106 | **Example: Email Module**
107 |
108 | `email_module.py`
109 |
110 | ```python
111 | from nest.core import Module
112 | from src.providers.s3.s3_module import S3Module
113 | from src.providers.s3.s3_service import S3Service
114 | from .email_service import EmailService
115 |
116 | @Module(
117 | imports=[S3Module],
118 | providers=[EmailService, S3Service],
119 | exports=[EmailService],
120 | )
121 | class EmailModule:
122 | pass
123 | ```
124 |
125 | In this example:
126 |
127 | The EmailModule imports the S3Module to use the S3Service provided by it.
128 |
129 | The EmailService is defined as a provider in the EmailModule and exported for use in other modules.
130 |
131 | ## Conclusion
132 |
133 | Providers are essential parts in PyNest applications, handling business logic and other functionalities.
134 | By defining, importing, exporting,
135 | and injecting providers, you can create modular and maintainable applications with clear separation of concerns.
136 | Understanding how providers work and how to use them effectively will help
137 | you build robust and scalable applications with PyNest.
138 |
139 | ---
140 |
141 |
--------------------------------------------------------------------------------
/docs/styles/extra.css:
--------------------------------------------------------------------------------
1 | nav.md-footer-nav {
2 | display: flex;
3 | justify-content: space-between;
4 | padding: 1rem 0;
5 | }
6 |
7 | nav.md-footer-nav a.md-footer-nav__link {
8 | display: flex;
9 | align-items: center;
10 | }
11 |
12 | nav.md-footer-nav a.md-footer-nav__link span {
13 | display: flex;
14 | align-items: center;
15 | }
16 |
17 | .md-header__button.md-logo img, .md-header__button.md-logo svg {
18 | height: 2rem;
19 | width: 2rem;
20 | }
21 | .md-header__button.md-logo {
22 | margin: 0;
23 | padding: 0;
24 | }
--------------------------------------------------------------------------------
/examples/BlankApp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/BlankApp/__init__.py
--------------------------------------------------------------------------------
/examples/BlankApp/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | if __name__ == "__main__":
4 | uvicorn.run("src.app_module:http_server", host="0.0.0.0", port=8010, reload=True)
5 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/BlankApp/src/__init__.py
--------------------------------------------------------------------------------
/examples/BlankApp/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get
2 |
3 | from .app_service import AppService
4 |
5 |
6 | @Controller("/")
7 | class AppController:
8 | def __init__(self, service: AppService):
9 | self.service = service
10 |
11 | @Get("/")
12 | def get_app_info(self):
13 | return self.service.get_app_info()
14 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/app_module.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | from nest.core import Module, PyNestFactory
4 |
5 | from .app_controller import AppController
6 | from .app_service import AppService
7 | from .example.example_module import ExampleModule
8 | from .product.product_module import ProductModule
9 | from .user.user_module import UserModule
10 |
11 |
12 | @Module(
13 | imports=[ExampleModule, UserModule, ProductModule],
14 | controllers=[AppController],
15 | providers=[AppService],
16 | )
17 | class AppModule:
18 | pass
19 |
20 |
21 | app = PyNestFactory.create(
22 | AppModule,
23 | description="This is my FastAPI app.",
24 | title="My App",
25 | version="1.0.0",
26 | debug=True,
27 | )
28 |
29 | http_server: FastAPI = app.get_server()
30 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class AppService:
6 | def __init__(self):
7 | self.app_name = "BlankApp"
8 | self.app_version = "1.0.0"
9 |
10 | def get_app_info(self):
11 | return {"app_name": self.app_name, "app_version": self.app_version}
12 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/BlankApp/src/example/__init__.py
--------------------------------------------------------------------------------
/examples/BlankApp/src/example/example_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get, HttpCode, Post
2 |
3 | from .example_model import Example
4 | from .example_service import ExampleService
5 |
6 |
7 | @Controller("example")
8 | class ExampleController:
9 | def __init__(self, service: ExampleService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_example(self):
14 | return self.service.get_example()
15 |
16 | @Post("/")
17 | @HttpCode(201)
18 | def add_example(self, example: Example):
19 | return self.service.add_example(example)
20 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/example/example_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Example(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/example/example_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .example_controller import ExampleController
4 | from .example_service import ExampleService
5 |
6 |
7 | @Module(controllers=[ExampleController], providers=[ExampleService], imports=[])
8 | class ExampleModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/example/example_service.py:
--------------------------------------------------------------------------------
1 | from nest.core.decorators import Injectable
2 |
3 | from ..user.user_service import UserService
4 | from .example_model import Example
5 |
6 |
7 | @Injectable
8 | class ExampleService:
9 | def __init__(self, user_service: UserService):
10 | self.database = []
11 | self.user_service = user_service
12 |
13 | def get_example(self):
14 | return self.database
15 |
16 | def add_example(self, example: Example):
17 | self.database.append(example)
18 | return example
19 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/product/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/BlankApp/src/product/__init__.py
--------------------------------------------------------------------------------
/examples/BlankApp/src/product/product_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get, Post
2 |
3 | from .product_model import Product
4 | from .product_service import ProductService
5 |
6 |
7 | @Controller("product")
8 | class ProductController:
9 | def __init__(self, service: ProductService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_product(self):
14 | return self.service.get_product()
15 |
16 | @Post("/")
17 | def add_product(self, product: Product):
18 | return self.service.add_product(product)
19 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/product/product_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Product(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/product/product_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .product_controller import ProductController
4 | from .product_service import ProductService
5 |
6 |
7 | @Module(controllers=[ProductController], providers=[ProductService], imports=[])
8 | class ProductModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/product/product_service.py:
--------------------------------------------------------------------------------
1 | from nest.core.decorators import Injectable
2 |
3 | from .product_model import Product
4 |
5 |
6 | @Injectable
7 | class ProductService:
8 | def __init__(self):
9 | self.database = []
10 |
11 | def get_product(self):
12 | return self.database
13 |
14 | def add_product(self, product: Product):
15 | self.database.append(product)
16 | return product
17 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/BlankApp/src/user/__init__.py
--------------------------------------------------------------------------------
/examples/BlankApp/src/user/user_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .user_model import User
4 | from .user_service import UserService
5 |
6 |
7 | @Controller("user")
8 | class UserController:
9 | def __init__(self, service: UserService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_user(self):
14 | return self.service.get_user()
15 |
16 | @Post("/")
17 | def add_user(self, user: User):
18 | return self.service.add_user(user)
19 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/user/user_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class User(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/user/user_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .user_controller import UserController
4 | from .user_service import UserService
5 |
6 |
7 | @Module(controllers=[UserController], providers=[UserService], imports=[])
8 | class UserModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/BlankApp/src/user/user_service.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from nest.core.decorators import Injectable
4 |
5 | from .user_model import User
6 |
7 |
8 | @Injectable
9 | class UserService:
10 | def __init__(self):
11 | self.database = []
12 | time.sleep(5)
13 | print("UserService initialized")
14 |
15 | def get_user(self):
16 | return self.database
17 |
18 | def add_user(self, user: User):
19 | self.database.append(user)
20 | return user
21 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/CommandLineApp/__init__.py
--------------------------------------------------------------------------------
/examples/CommandLineApp/main.py:
--------------------------------------------------------------------------------
1 | from src.app_module import app
2 |
3 | if __name__ == "__main__":
4 | app()
5 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/CommandLineApp/src/__init__.py
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
2 |
3 | from .app_service import AppService
4 |
5 |
6 | @CliController("app")
7 | class AppController:
8 |
9 | def __init__(self, app_service: AppService):
10 | self.app_service = app_service
11 |
12 | @CliCommand("info")
13 | def get_app_info(self):
14 | app_info = self.app_service.get_app_info()
15 | print(app_info)
16 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/app_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 | from nest.core.cli_factory import CLIAppFactory
3 | from nest.core.pynest_factory import PyNestFactory
4 |
5 | from .app_controller import AppController
6 | from .app_service import AppService
7 | from .user.user_module import UserModule
8 | from .user.user_service import UserService
9 |
10 |
11 | @Module(
12 | imports=[UserModule],
13 | controllers=[AppController],
14 | providers=[UserService, AppService],
15 | )
16 | class AppModule:
17 | pass
18 |
19 |
20 | app = CLIAppFactory().create(AppModule)
21 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class AppService:
6 | def __init__(self):
7 | self.app_name = "Pynest App"
8 | self.app_version = "1.0.0"
9 |
10 | def get_app_info(self):
11 | return {"app_name": self.app_name, "app_version": self.app_version}
12 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/CommandLineApp/src/user/__init__.py
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/user/user_controller.py:
--------------------------------------------------------------------------------
1 | import click
2 |
3 | from examples.CommandLineApp.src.user.user_service import UserService
4 | from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
5 |
6 |
7 | class ListOptions:
8 | USER = click.Option(
9 | ["-u", "--user"], help="user name to retrieve", required=True, type=str
10 | )
11 |
12 |
13 | @CliController("user")
14 | class UserController:
15 |
16 | def __init__(self, user_service: UserService):
17 | self.user_service = user_service
18 |
19 | @CliCommand("list", help="List all users")
20 | def list_users(self):
21 | return self.user_service.get_users()
22 |
23 | @CliCommand("show", help="Show user by id")
24 | def show_user(
25 | self,
26 | user: ListOptions.USER,
27 | ):
28 | return self.user_service.get_user(user)
29 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/user/user_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .user_controller import UserController
4 | from .user_service import UserService
5 |
6 |
7 | @Module(controllers=[UserController], providers=[UserService])
8 | class UserModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/CommandLineApp/src/user/user_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class UserService:
6 |
7 | def get_users(self):
8 | print("This command show all users")
9 |
10 | def get_user(self, user_name: str):
11 | print(f"This command show user with user_name: {user_name}")
12 |
--------------------------------------------------------------------------------
/examples/MongoApp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/MongoApp/__init__.py
--------------------------------------------------------------------------------
/examples/MongoApp/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | if __name__ == "__main__":
4 | uvicorn.run("src.app_module:http_server", host="0.0.0.0", port=8000, reload=True)
5 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/MongoApp/src/__init__.py
--------------------------------------------------------------------------------
/examples/MongoApp/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get
2 |
3 | from .app_service import AppService
4 |
5 |
6 | @Controller("/")
7 | class AppController:
8 | def __init__(self, service: AppService):
9 | self.service = service
10 |
11 | @Get("/")
12 | def get_app_info(self):
13 | return self.service.get_app_info()
14 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/app_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module, PyNestFactory
2 |
3 | from .app_controller import AppController
4 | from .app_service import AppService
5 | from .config import config
6 | from .example.example_module import ExampleModule
7 | from .product.product_module import ProductModule
8 | from .user.user_module import UserModule
9 |
10 |
11 | @Module(
12 | imports=[ExampleModule, UserModule, ProductModule],
13 | controllers=[AppController],
14 | providers=[AppService],
15 | )
16 | class AppModule:
17 | pass
18 |
19 |
20 | app = PyNestFactory.create(
21 | AppModule,
22 | description="This is my FastAPI app drive by MongoDB Engine",
23 | title="My App",
24 | version="1.0.0",
25 | debug=True,
26 | )
27 |
28 | http_server = app.get_server()
29 |
30 |
31 | @http_server.on_event("startup")
32 | async def startup():
33 | await config.create_all()
34 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class AppService:
6 | def __init__(self):
7 | self.app_name = "MongoApp"
8 | self.app_version = "1.0.0"
9 |
10 | def get_app_info(self):
11 | return {"app_name": self.app_name, "app_version": self.app_version}
12 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import load_dotenv
4 |
5 | from nest.core.database.odm_provider import OdmProvider
6 |
7 | from .example.example_entity import Example
8 | from .product.product_entity import Product
9 | from .user.user_entity import User
10 |
11 | load_dotenv()
12 | config = OdmProvider(
13 | config_params={
14 | "db_name": os.getenv("DB_NAME", "default_nest_db"),
15 | "host": os.getenv("DB_HOST", "localhost"),
16 | "port": os.getenv("DB_PORT", 27017),
17 | },
18 | document_models=[User, Product, Example],
19 | )
20 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/MongoApp/src/example/__init__.py
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/example_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get, Post
2 |
3 | from .example_model import Example
4 | from .example_service import ExampleService
5 |
6 |
7 | @Controller("example")
8 | class ExampleController:
9 | def __init__(self, service: ExampleService):
10 | self.service = service
11 |
12 | @Get("/")
13 | async def get_example(self):
14 | return await self.service.get_example()
15 |
16 | @Post("/")
17 | async def add_example(self, example: Example):
18 | return await self.service.add_example(example)
19 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/example_entity.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 |
4 | class Example(Document):
5 | name: str
6 |
7 | class Config:
8 | schema_extra = {
9 | "example": {
10 | "name": "Example Name",
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/example_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Example(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/example_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .example_controller import ExampleController
4 | from .example_service import ExampleService
5 |
6 |
7 | @Module(controllers=[ExampleController], providers=[ExampleService], imports=[])
8 | class ExampleModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/example/example_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from .example_entity import Example as ExampleEntity
5 | from .example_model import Example
6 |
7 | # import asyncio # Uncomment this line to test the async nature of the service
8 |
9 |
10 | @Injectable
11 | class ExampleService:
12 | @db_request_handler
13 | async def add_example(self, example: Example):
14 | new_example = ExampleEntity(**example.dict())
15 | await new_example.save()
16 | return new_example.id
17 |
18 | @db_request_handler
19 | async def get_example(self):
20 | # await asyncio.sleep(5)
21 | return await ExampleEntity.find_all().to_list()
22 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/MongoApp/src/product/__init__.py
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/product_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .product_model import Product
4 | from .product_service import ProductService
5 |
6 |
7 | @Controller("product")
8 | class ProductController:
9 | def __init__(self, service: ProductService):
10 | self.service = service
11 |
12 | @Get("/")
13 | async def get_product(self):
14 | return await self.service.get_product()
15 |
16 | @Post("/")
17 | async def add_product(self, product: Product):
18 | return await self.service.add_product(product)
19 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/product_entity.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 |
4 | class Product(Document):
5 | name: str
6 |
7 | class Config:
8 | schema_extra = {
9 | "example": {
10 | "name": "Example Name",
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/product_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Product(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/product_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .product_controller import ProductController
4 | from .product_service import ProductService
5 |
6 |
7 | @Module(controllers=[ProductController], providers=[ProductService], imports=[])
8 | class ProductModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/product/product_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from .product_entity import Product as ProductEntity
5 | from .product_model import Product
6 |
7 |
8 | @Injectable
9 | class ProductService:
10 | @db_request_handler
11 | async def add_product(self, product: Product):
12 | new_product = ProductEntity(**product.dict())
13 | await new_product.save()
14 | return new_product.id
15 |
16 | @db_request_handler
17 | async def get_product(self):
18 | return await ProductEntity.find_all().to_list()
19 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/MongoApp/src/user/__init__.py
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/user_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .user_model import User
4 | from .user_service import UserService
5 |
6 |
7 | @Controller("user")
8 | class UserController:
9 | def __init__(self, service: UserService):
10 | self.service = service
11 |
12 | @Get("/")
13 | async def get_user(self):
14 | return await self.service.get_user()
15 |
16 | @Post("/")
17 | async def add_user(self, user: User):
18 | return await self.service.add_user(user)
19 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/user_entity.py:
--------------------------------------------------------------------------------
1 | from beanie import Document
2 |
3 |
4 | class User(Document):
5 | name: str
6 |
7 | class Config:
8 | schema_extra = {
9 | "example": {
10 | "name": "Example Name",
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/user_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class User(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/user_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .user_controller import UserController
4 | from .user_service import UserService
5 |
6 |
7 | @Module(controllers=[UserController], providers=[UserService], imports=[])
8 | class UserModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/MongoApp/src/user/user_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from .user_entity import User as UserEntity
5 | from .user_model import User
6 |
7 |
8 | @Injectable
9 | class UserService:
10 | @db_request_handler
11 | async def add_user(self, user: User):
12 | new_user = UserEntity(**user.dict())
13 | await new_user.save()
14 | return new_user.id
15 |
16 | @db_request_handler
17 | async def get_user(self):
18 | return await UserEntity.find_all().to_list()
19 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmAsyncApp/__init__.py
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | if __name__ == "__main__":
4 | uvicorn.run("src.app_module:http_server", host="0.0.0.0", port=8001, reload=True)
5 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmAsyncApp/src/__init__.py
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get
2 |
3 | from .app_service import AppService
4 |
5 |
6 | @Controller("/")
7 | class AppController:
8 | def __init__(self, service: AppService):
9 | self.service = service
10 |
11 | @Get("/")
12 | async def get_app_info(self):
13 | return await self.service.get_app_info()
14 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/app_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module, PyNestFactory
2 |
3 | from .app_controller import AppController
4 | from .app_service import AppService
5 | from .config import config
6 | from .example.example_module import ExampleModule
7 | from .product.product_module import ProductModule
8 | from .user.user_module import UserModule
9 |
10 |
11 | @Module(
12 | imports=[ExampleModule, UserModule, ProductModule],
13 | controllers=[AppController],
14 | providers=[AppService],
15 | )
16 | class AppModule:
17 | pass
18 |
19 |
20 | app = PyNestFactory.create(
21 | AppModule,
22 | description="This is my FastAPI app drive by Async ORM Engine",
23 | title="My App",
24 | version="1.0.0",
25 | debug=True,
26 | )
27 |
28 | http_server = app.get_server()
29 |
30 |
31 | @http_server.on_event("startup")
32 | async def startup():
33 | await config.create_all()
34 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class AppService:
6 | def __init__(self):
7 | self.app_name = "MongoApp"
8 | self.app_version = "1.0.0"
9 |
10 | async def get_app_info(self):
11 | return {"app_name": self.app_name, "app_version": self.app_version}
12 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import load_dotenv
4 |
5 | from nest.core.database.orm_provider import AsyncOrmProvider
6 |
7 | load_dotenv()
8 |
9 | config = AsyncOrmProvider(
10 | db_type="sqlite",
11 | config_params=dict(
12 | db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db"),
13 | ),
14 | )
15 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmAsyncApp/src/example/__init__.py
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/example_controller.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.asyncio import AsyncSession
2 |
3 | from nest.core import Controller, Depends, Get, Post
4 |
5 | from ..config import config
6 | from .example_model import Example
7 | from .example_service import ExampleService
8 |
9 |
10 | @Controller("example")
11 | class ExampleController:
12 | def __init__(self, service: ExampleService):
13 | self.service = service
14 |
15 | @Get("/")
16 | async def get_example(self, session: AsyncSession = Depends(config.get_db)):
17 | return await self.service.get_example(session)
18 |
19 | @Post("/")
20 | async def add_example(
21 | self, example: Example, session: AsyncSession = Depends(config.get_db)
22 | ):
23 | return await self.service.add_example(example, session)
24 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/example_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class Example(config.Base):
8 | __tablename__ = "example"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/example_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Example(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/example_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .example_controller import ExampleController
4 | from .example_service import ExampleService
5 |
6 |
7 | @Module(controllers=[ExampleController], providers=[ExampleService], imports=[])
8 | class ExampleModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/example/example_service.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import select
2 | from sqlalchemy.ext.asyncio import AsyncSession
3 |
4 | from nest.core import Injectable
5 | from nest.core.decorators.database import async_db_request_handler
6 |
7 | from .example_entity import Example as ExampleEntity
8 | from .example_model import Example
9 |
10 |
11 | @Injectable
12 | class ExampleService:
13 | @async_db_request_handler
14 | async def add_example(self, example: Example, session: AsyncSession):
15 | new_example = ExampleEntity(**example.dict())
16 | session.add(new_example)
17 | await session.commit()
18 | return new_example.id
19 |
20 | @async_db_request_handler
21 | async def get_example(self, session: AsyncSession):
22 | query = select(ExampleEntity)
23 | result = await session.execute(query)
24 | return result.scalars().all()
25 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmAsyncApp/src/product/__init__.py
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/product_controller.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.asyncio import AsyncSession
2 |
3 | from nest.core import Controller, Depends, Get, Post
4 |
5 | from ..config import config
6 | from .product_model import Product
7 | from .product_service import ProductService
8 |
9 |
10 | @Controller("product")
11 | class ProductController:
12 | def __init__(self, service: ProductService):
13 | self.service = service
14 |
15 | @Get("/")
16 | async def get_product(self, session: AsyncSession = Depends(config.get_db)):
17 | return await self.service.get_product(session)
18 |
19 | @Post("/")
20 | async def add_product(
21 | self, product: Product, session: AsyncSession = Depends(config.get_db)
22 | ):
23 | return await self.service.add_product(product, session)
24 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/product_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class Product(config.Base):
8 | __tablename__ = "product"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/product_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Product(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/product_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .product_controller import ProductController
4 | from .product_service import ProductService
5 |
6 |
7 | @Module(controllers=[ProductController], providers=[ProductService], imports=[])
8 | class ProductModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/product/product_service.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import select
2 | from sqlalchemy.ext.asyncio import AsyncSession
3 |
4 | from nest.core import Injectable
5 | from nest.core.decorators.database import async_db_request_handler
6 |
7 | from .product_entity import Product as ProductEntity
8 | from .product_model import Product
9 |
10 |
11 | @Injectable
12 | class ProductService:
13 | @async_db_request_handler
14 | async def add_product(self, product: Product, session: AsyncSession):
15 | new_product = ProductEntity(**product.dict())
16 | session.add(new_product)
17 | await session.commit()
18 | return new_product.id
19 |
20 | @async_db_request_handler
21 | async def get_product(self, session: AsyncSession):
22 | query = select(ProductEntity)
23 | result = await session.execute(query)
24 | return result.scalars().all()
25 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmAsyncApp/src/user/__init__.py
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/user_controller.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.asyncio import AsyncSession
2 |
3 | from nest.core import Controller, Depends, Get, Post
4 |
5 | from ..config import config
6 | from .user_model import User
7 | from .user_service import UserService
8 |
9 |
10 | @Controller("user")
11 | class UserController:
12 | def __init__(self, service: UserService):
13 | self.service = service
14 |
15 | @Get("/")
16 | async def get_user(self, session: AsyncSession = Depends(config.get_db)):
17 | return await self.service.get_user(session)
18 |
19 | @Post("/")
20 | async def add_user(
21 | self, user: User, session: AsyncSession = Depends(config.get_db)
22 | ):
23 | return await self.service.add_user(user, session)
24 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/user_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class User(config.Base):
8 | __tablename__ = "user"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/user_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class User(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/user_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .user_controller import UserController
4 | from .user_service import UserService
5 |
6 |
7 | @Module(controllers=[UserController], providers=[UserService], imports=[])
8 | class UserModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmAsyncApp/src/user/user_service.py:
--------------------------------------------------------------------------------
1 | """
2 | To Test the asynchronous database operations, uncomment the commented lines in the code below and run the application.
3 | """
4 |
5 | from sqlalchemy import select
6 | from sqlalchemy.ext.asyncio import AsyncSession
7 |
8 | from nest.core import Injectable
9 | from nest.core.decorators.database import async_db_request_handler
10 |
11 | from .user_entity import User as UserEntity
12 | from .user_model import User
13 |
14 | # import asyncio
15 |
16 |
17 | @Injectable
18 | class UserService:
19 | @async_db_request_handler
20 | async def add_user(self, user: User, session: AsyncSession):
21 | new_user = UserEntity(**user.dict())
22 | session.add(new_user)
23 | await session.commit()
24 | return new_user.id
25 |
26 | @async_db_request_handler
27 | async def get_user(self, session: AsyncSession):
28 | query = select(UserEntity)
29 | result = await session.execute(query)
30 | # await asyncio.sleep(5)
31 | return result.scalars().all()
32 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmSyncApp/__init__.py
--------------------------------------------------------------------------------
/examples/OrmSyncApp/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | if __name__ == "__main__":
4 | uvicorn.run("src.app_module:http_server", host="0.0.0.0", port=8000, reload=True)
5 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmSyncApp/src/__init__.py
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Get
2 |
3 | from .app_service import AppService
4 |
5 |
6 | @Controller("/")
7 | class AppController:
8 | def __init__(self, service: AppService):
9 | self.service = service
10 |
11 | @Get("/")
12 | def get_app_info(self):
13 | return self.service.get_app_info()
14 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/app_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module, PyNestFactory
2 |
3 | from .app_controller import AppController
4 | from .app_service import AppService
5 | from .config import config
6 | from .example.example_module import ExampleModule
7 | from .product.product_module import ProductModule
8 | from .user.user_module import UserModule
9 |
10 |
11 | @Module(
12 | imports=[ExampleModule, UserModule, ProductModule],
13 | controllers=[AppController],
14 | providers=[AppService],
15 | )
16 | class AppModule:
17 | pass
18 |
19 |
20 | app = PyNestFactory.create(
21 | AppModule,
22 | description="This is my FastAPI app drive by Sync ORM Engine",
23 | title="My App",
24 | version="1.0.0",
25 | debug=True,
26 | )
27 |
28 | http_server = app.get_server()
29 |
30 |
31 | @http_server.on_event("startup")
32 | def startup():
33 | config.create_all()
34 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 |
3 |
4 | @Injectable
5 | class AppService:
6 | def __init__(self):
7 | self.app_name = "SyncORMApp"
8 | self.app_version = "1.0.0"
9 |
10 | def get_app_info(self):
11 | return {"app_name": self.app_name, "app_version": self.app_version}
12 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import load_dotenv
4 |
5 | from nest.core.database.orm_provider import OrmProvider
6 |
7 | load_dotenv()
8 |
9 | config = OrmProvider(
10 | db_type="sqlite",
11 | config_params=dict(
12 | db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db"),
13 | ),
14 | )
15 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmSyncApp/src/example/__init__.py
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/example_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .example_model import Example
4 | from .example_service import ExampleService
5 |
6 |
7 | @Controller("example")
8 | class ExampleController:
9 | def __init__(self, service: ExampleService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_example(self):
14 | return self.service.get_example()
15 |
16 | @Post("/")
17 | def add_example(self, example: Example):
18 | return self.service.add_example(example)
19 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/example_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class Example(config.Base):
8 | __tablename__ = "example"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/example_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Example(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/example_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .example_controller import ExampleController
4 | from .example_service import ExampleService
5 |
6 |
7 | @Module(controllers=[ExampleController], providers=[ExampleService], imports=[])
8 | class ExampleModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/example/example_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from ..config import config
5 | from .example_entity import Example as ExampleEntity
6 | from .example_model import Example
7 |
8 |
9 | @Injectable
10 | class ExampleService:
11 | def __init__(self):
12 | self.config = config
13 | self.session = self.config.get_db()
14 |
15 | @db_request_handler
16 | def add_example(self, example: Example):
17 | new_example = ExampleEntity(**example.dict())
18 | self.session.add(new_example)
19 | self.session.commit()
20 | return new_example.id
21 |
22 | @db_request_handler
23 | def get_example(self):
24 | return self.session.query(ExampleEntity).all()
25 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmSyncApp/src/product/__init__.py
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/product_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .product_model import Product
4 | from .product_service import ProductService
5 |
6 |
7 | @Controller("product")
8 | class ProductController:
9 | def __init__(self, service: ProductService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_products(self):
14 | return self.service.get_products()
15 |
16 | @Post("/")
17 | def add_product(self, product: Product):
18 | return self.service.add_product(product)
19 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/product_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class Product(config.Base):
8 | __tablename__ = "product"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/product_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class Product(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/product_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .product_controller import ProductController
4 | from .product_service import ProductService
5 |
6 |
7 | @Module(controllers=[ProductController], providers=[ProductService], imports=[])
8 | class ProductModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/product/product_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from ..config import config
5 | from .product_entity import Product as ProductEntity
6 | from .product_model import Product
7 |
8 |
9 | @Injectable
10 | class ProductService:
11 | def __init__(self):
12 | self.config = config
13 | self.session = self.config.get_db()
14 |
15 | @db_request_handler
16 | def add_product(self, product: Product):
17 | new_product = ProductEntity(**product.dict())
18 | self.session.add(new_product)
19 | self.session.commit()
20 | return new_product.id
21 |
22 | @db_request_handler
23 | def get_products(self):
24 | return self.session.query(ProductEntity).all()
25 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/OrmSyncApp/src/user/__init__.py
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/user_controller.py:
--------------------------------------------------------------------------------
1 | from nest.core import Controller, Depends, Get, Post
2 |
3 | from .user_model import User
4 | from .user_service import UserService
5 |
6 |
7 | @Controller("user")
8 | class UserController:
9 | def __init__(self, service: UserService):
10 | self.service = service
11 |
12 | @Get("/")
13 | def get_users(self):
14 | return self.service.get_users()
15 |
16 | @Post("/")
17 | def add_user(self, user: User):
18 | return self.service.add_user(user)
19 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/user_entity.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Integer, String
2 | from sqlalchemy.orm import Mapped, mapped_column
3 |
4 | from ..config import config
5 |
6 |
7 | class User(config.Base):
8 | __tablename__ = "user"
9 |
10 | id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
11 | name: Mapped[str] = mapped_column(String, unique=True)
12 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/user_model.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class User(BaseModel):
5 | name: str
6 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/user_module.py:
--------------------------------------------------------------------------------
1 | from nest.core import Module
2 |
3 | from .user_controller import UserController
4 | from .user_service import UserService
5 |
6 |
7 | @Module(controllers=[UserController], providers=[UserService], imports=[])
8 | class UserModule:
9 | pass
10 |
--------------------------------------------------------------------------------
/examples/OrmSyncApp/src/user/user_service.py:
--------------------------------------------------------------------------------
1 | from nest.core import Injectable
2 | from nest.core.decorators.database import db_request_handler
3 |
4 | from ..config import config
5 | from .user_entity import User as UserEntity
6 | from .user_model import User
7 |
8 |
9 | @Injectable
10 | class UserService:
11 | def __init__(self):
12 | self.config = config
13 | self.session = self.config.get_db()
14 |
15 | @db_request_handler
16 | def add_user(self, user: User):
17 | new_user = UserEntity(**user.dict())
18 | self.session.add(new_user)
19 | self.session.commit()
20 | return new_user.id
21 |
22 | @db_request_handler
23 | def get_users(self):
24 | return self.session.query(UserEntity).all()
25 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/examples/__init__.py
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: PyNest
2 |
3 | site_url: https://PyNest.github.io/PyNest/
4 | docs_dir: docs
5 | repo_url: https://github.com/PythonNest/PyNest
6 | repo_name: Pynest
7 |
8 |
9 | theme:
10 | favicon: imgs/pynest-logo.png
11 | name: material
12 | logo: imgs/pynest-logo.png
13 | features:
14 | - content.code.copy
15 | - navigation.tabs
16 | icon:
17 | repo: fontawesome/brands/github-alt
18 | palette:
19 | - media: "(prefers-color-scheme: light)"
20 | scheme: default
21 | primary: teal
22 | accent: amber
23 | toggle:
24 | icon: material/toggle-switch
25 | name: Switch to dark mode
26 | # Palette toggle for dark mode
27 | - media: "(prefers-color-scheme: dark)"
28 | scheme: slate
29 | primary: teal
30 | accent: amber
31 | toggle:
32 | icon: material/toggle-switch-off-outline
33 | name: Switch to light mode
34 |
35 | plugins:
36 | - search
37 | - mkdocstrings
38 |
39 | markdown_extensions:
40 | - pymdownx.highlight:
41 | anchor_linenums: true
42 | - pymdownx.inlinehilite
43 | - pymdownx.snippets
44 | - pymdownx.superfences
45 |
46 |
47 |
48 | extra_css:
49 | - styles/extra.css
50 |
51 | nav:
52 | - Overview:
53 | - Introduction: introduction.md
54 | - Getting Started: getting_started.md
55 | - CLI Usage: cli.md
56 | - Modules: modules.md
57 | - Controllers: controllers.md
58 | - Providers: providers.md
59 | - Guards: guards.md
60 | - Dependency Injection: dependency_injection.md
61 | - Deployment:
62 | - Docker: docker.md
63 | - Application Examples:
64 | - Blank Application: blank.md
65 | - Sync ORM Application: sync_orm.md
66 | - Async ORM Application: async_orm.md
67 | - MongoDB Application: mongodb.md
68 | - License: license.md
--------------------------------------------------------------------------------
/nest/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.3.1"
2 |
--------------------------------------------------------------------------------
/nest/cli/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/cli/__init__.py
--------------------------------------------------------------------------------
/nest/cli/cli.py:
--------------------------------------------------------------------------------
1 | from nest.cli.src.app_module import nest_cli
2 |
3 | if __name__ == "__main__":
4 | nest_cli()
5 |
--------------------------------------------------------------------------------
/nest/cli/click_handlers.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import yaml
4 |
5 | from nest.common.templates.templates_factory import TemplateFactory
6 |
7 |
8 | def get_metadata():
9 | setting_path = Path(__file__).parent.parent / "settings.yaml"
10 | assert setting_path.exists(), "settings.yaml file not found"
11 | with open(setting_path, "r") as file:
12 | file = yaml.load(file, Loader=yaml.FullLoader)
13 |
14 | config = file["config"]
15 | db_type = config["db_type"]
16 | is_async = config["is_async"]
17 | return db_type, is_async
18 |
19 |
20 | def create_nest_app(app_name: str = ".", db_type: str = None, is_async: bool = False):
21 | """
22 | Create a new nest app
23 |
24 | :param app_name: The name of the app
25 | :param db_type: The type of the database (sqlite, mysql, postgresql)
26 | :param is_async: whether the project should be async or not (only for relational databases)
27 |
28 | The files structure are:
29 |
30 | ├── app_module.py
31 | ├── config.py (only for databases)
32 | ├── main.py
33 | ├── requirements.txt
34 | ├── .gitignore
35 | ├── src
36 | │ ├── __init__.py
37 |
38 | in addition to those files, a setting.yaml file will be created in the package level that will help managed configurations
39 | """
40 | template_factory = TemplateFactory()
41 | template = template_factory.get_template(
42 | module_name="example", db_type=db_type, is_async=is_async
43 | )
44 | template.generate_project(app_name)
45 |
46 |
47 | def create_nest_module(name: str):
48 | """
49 | Create a new nest module
50 |
51 | :param name: The name of the module
52 |
53 | The files structure are:
54 | ├── ...
55 | ├── src
56 | │ ├── __init__.py
57 | │ ├── module_name
58 | ├── __init__.py
59 | ├── module_name_controller.py
60 | ├── module_name_service.py
61 | ├── module_name_model.py
62 | ├── module_name_entity.py (only for databases)
63 | ├── module_name_module.py
64 | """
65 | db_type, is_async = get_metadata()
66 | template_factory = TemplateFactory()
67 | template = template_factory.get_template(
68 | module_name=name, db_type=db_type, is_async=is_async
69 | )
70 | template.generate_module(name)
71 |
--------------------------------------------------------------------------------
/nest/cli/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/cli/src/__init__.py
--------------------------------------------------------------------------------
/nest/cli/src/app_controller.py:
--------------------------------------------------------------------------------
1 | from nest.cli.src.app_service import AppService
2 | from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
3 |
4 |
5 | @CliController("app")
6 | class AppController:
7 | def __init__(self, app_service: AppService):
8 | self.app_service = app_service
9 |
10 | @CliCommand("info")
11 | def get_app_info(self):
12 | app_info = self.app_service.get_app_info()
13 | print(app_info)
14 |
15 | @CliCommand("version", help="Get the version of the app")
16 | def get_app_version(self):
17 | app_info = self.app_service.get_app_info()
18 | print(app_info["app_version"])
19 |
--------------------------------------------------------------------------------
/nest/cli/src/app_module.py:
--------------------------------------------------------------------------------
1 | from nest.cli.src.app_controller import AppController
2 | from nest.cli.src.app_service import AppService
3 | from nest.cli.src.generate.generate_module import GenerateModule
4 | from nest.core import Module
5 | from nest.core.cli_factory import CLIAppFactory
6 |
7 |
8 | @Module(
9 | imports=[GenerateModule],
10 | controllers=[AppController],
11 | providers=[AppService],
12 | )
13 | class AppModule:
14 | pass
15 |
16 |
17 | nest_cli = CLIAppFactory().create(AppModule)
18 |
--------------------------------------------------------------------------------
/nest/cli/src/app_service.py:
--------------------------------------------------------------------------------
1 | from nest import __version__
2 | from nest.core import Injectable
3 |
4 |
5 | @Injectable
6 | class AppService:
7 | def __init__(self):
8 | self.app_name = "PyNest CLI App"
9 | self.app_version = __version__
10 |
11 | def get_app_info(self):
12 | return {"app_name": self.app_name, "app_version": self.app_version}
13 |
--------------------------------------------------------------------------------
/nest/cli/src/generate/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/cli/src/generate/__init__.py
--------------------------------------------------------------------------------
/nest/cli/src/generate/generate_controller.py:
--------------------------------------------------------------------------------
1 | import click
2 | from click import Option
3 |
4 | from nest.cli.src.generate.generate_model import SharedOptions
5 | from nest.cli.src.generate.generate_service import GenerateService
6 | from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
7 |
8 |
9 | @CliController("generate")
10 | class GenerateController:
11 | def __init__(self, generate_service: GenerateService):
12 | self.generate_service = generate_service
13 |
14 | @CliCommand("resource")
15 | def generate_resource(
16 | self,
17 | name: SharedOptions.NAME,
18 | path: SharedOptions.PATH,
19 | ):
20 | self.generate_service.generate_resource(name, path)
21 |
22 | @CliCommand("controller", help="Generate a new nest controller")
23 | def generate_controller(self, name: SharedOptions.NAME, path: SharedOptions.PATH):
24 | self.generate_service.generate_controller(name, path)
25 |
26 | @CliCommand("service", help="Generate a new nest service")
27 | def generate_service(self, name: SharedOptions.NAME, path: SharedOptions.PATH):
28 | self.generate_service.generate_service(name, path)
29 |
30 | @CliCommand("module", help="Generate a new nest module")
31 | def generate_module(self, name: SharedOptions.NAME):
32 | self.generate_service.generate_module(name)
33 |
34 | @CliCommand("application", help="Generate a new nest application")
35 | def generate_app(
36 | self,
37 | app_name: SharedOptions.APP_NAME,
38 | db_type: SharedOptions.DB_TYPE,
39 | is_async: SharedOptions.IS_ASYNC,
40 | is_cli: SharedOptions.IS_CLI,
41 | ):
42 | click.echo(f"Generating app {app_name}")
43 | self.generate_service.generate_app(app_name, db_type, is_async, is_cli)
44 |
--------------------------------------------------------------------------------
/nest/cli/src/generate/generate_model.py:
--------------------------------------------------------------------------------
1 | import click
2 | from click import Option
3 |
4 |
5 | class SharedOptions:
6 | NAME = Option(
7 | ["-n", "--name"],
8 | help="Name of the resource",
9 | required=True,
10 | type=str,
11 | )
12 | APP_NAME = Option(
13 | ["-n", "--app-name"],
14 | help="Name of the application",
15 | required=True,
16 | type=str,
17 | )
18 | PATH = Option(
19 | ["-p", "--path"], help="Path of the resource", required=False, type=str
20 | )
21 | DB_TYPE = Option(
22 | ["-db", "--db-type"],
23 | help="The type of the database (postgresql, mysql, sqlite, or mongo db).",
24 | required=False,
25 | show_choices=True,
26 | type=str,
27 | default=None,
28 | )
29 | IS_ASYNC = Option(
30 | ["--is-async"],
31 | help="Whether the project should be async or not (only for relational databases).",
32 | required=False,
33 | is_flag=True,
34 | default=False,
35 | )
36 | IS_CLI = Option(
37 | ["--is-cli"],
38 | help="Whether the project should be a CLI project or not.",
39 | required=False,
40 | is_flag=True,
41 | default=False,
42 | )
43 |
--------------------------------------------------------------------------------
/nest/cli/src/generate/generate_module.py:
--------------------------------------------------------------------------------
1 | from nest.cli.src.generate.generate_controller import GenerateController
2 | from nest.cli.src.generate.generate_service import GenerateService
3 | from nest.core import Module
4 |
5 |
6 | @Module(controllers=[GenerateController], providers=[GenerateService])
7 | class GenerateModule:
8 | pass
9 |
--------------------------------------------------------------------------------
/nest/cli/src/generate/generate_service.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import click
4 | import yaml
5 |
6 | from nest.cli.templates.templates_factory import TemplateFactory
7 | from nest.core import Injectable
8 |
9 |
10 | @Injectable
11 | class GenerateService:
12 | def __init__(self):
13 | self.template_factory = TemplateFactory()
14 |
15 | @staticmethod
16 | def get_metadata():
17 | config = {"db_type": None, "is_async": False, "is_cli": False}
18 | setting_path = Path(__file__).parent.parent.parent.parent / "settings.yaml"
19 | if setting_path.exists():
20 | with open(setting_path, "r") as file:
21 | file = yaml.load(file, Loader=yaml.FullLoader)
22 | config = file["config"]
23 | db_type = config["db_type"]
24 | is_async = config["is_async"]
25 | is_cli = config["is_cli"] if "is_cli" in config else False
26 | return db_type, is_async, is_cli
27 |
28 | def get_template(self, module_name: str):
29 | db_type, is_async, is_cli = self.get_metadata()
30 | template = self.template_factory.get_template(
31 | module_name=module_name, db_type=db_type, is_async=is_async, is_cli=is_cli
32 | )
33 | return template
34 |
35 | def generate_resource(self, name: str, path: str = None):
36 | """
37 | Create a new nest resource
38 |
39 | :param name: The name of the module
40 | :param path: The path where the module will be created, Must be in a scope of the src folder.
41 |
42 | The files structure are:
43 | ├── ...
44 | ├── src
45 | │ ├── __init__.py
46 | │ ├── module_name
47 | ├── __init__.py
48 | ├── module_name_controller.py
49 | ├── module_name_service.py
50 | ├── module_name_model.py
51 | ├── module_name_entity.py (only for databases)
52 | ├── module_name_module.py
53 |
54 | """
55 | if path is None:
56 | path = Path.cwd()
57 | template = self.get_template(name)
58 | template.generate_module(name, path)
59 |
60 | def generate_controller(self, name: str, path: str = None):
61 | """
62 | Create a new nest controller
63 |
64 | :param name: The name of the controller
65 |
66 | The files structure are:
67 | ├── ...
68 | ├── src
69 | │ ├── __init__.py
70 | │ ├── module_name
71 | ├── __init__.py
72 | ├── module_name_controller.py
73 | """
74 | template = self.get_template(name)
75 | if path is None:
76 | path = Path.cwd()
77 | with open(f"{path}/{name}_controller.py", "w") as f:
78 | f.write(template.generate_empty_controller_file())
79 |
80 | def generate_service(self, name: str, path: str = None):
81 | """
82 | Create a new nest service
83 |
84 | :param name: The name of the service
85 |
86 | The files structure are:
87 | ├── ...
88 | ├── src
89 | │ ├── __init__.py
90 | │ ├── module_name
91 | ├── __init__.py
92 | ├── module_name_service.py
93 | """
94 | template = self.get_template(name)
95 | if path is None:
96 | path = Path.cwd()
97 | with open(f"{path}/{name}_service.py", "w") as f:
98 | f.write(template.generate_empty_service_file())
99 |
100 | def generate_module(self, name: str, path: str = None):
101 | """
102 | Create a new nest module
103 |
104 | :param name: The name of the module
105 | :param path: The path where the module will be created
106 |
107 | The files structure are:
108 | ├── ...
109 | ├── src
110 | │ ├── __init__.py
111 | │ ├── module_name
112 | ├── __init__.py
113 | ├── module_name_module.py
114 | """
115 | template = self.get_template(name)
116 | if path is None:
117 | path = Path.cwd() / "src"
118 | with open(f"{path}/{name}_module.py", "w") as f:
119 | f.write(template.generate_empty_module_file())
120 |
121 | def generate_app(self, app_name: str, db_type: str, is_async: bool, is_cli: bool):
122 | """
123 | Create a new nest app
124 |
125 | :param app_name: The name of the app
126 | :param db_type: The type of the database
127 | :param is_async: Whether the project should be async or not
128 | :param is_cli: Whether the project should be a CLI project or not
129 |
130 | The files structure are:
131 | ├── ...
132 | ├── src
133 | │ ├── __init__.py
134 | │ ├── app_name
135 | ├── __init__.py
136 | ├── app_name_controller.py
137 | ├── app_name_service.py
138 | ├── app_name_model.py
139 | ├── app_name_entity.py (only for databases)
140 | ├── app_name_module.py
141 | """
142 | template = self.template_factory.get_template(
143 | module_name=app_name, db_type=db_type, is_async=is_async, is_cli=is_cli
144 | )
145 | template.generate_project(app_name)
146 |
--------------------------------------------------------------------------------
/nest/cli/templates/__init__.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class Database(Enum):
5 | POSTGRESQL = "postgresql"
6 | MYSQL = "mysql"
7 | SQLITE = "sqlite"
8 | MONGODB = "mongodb"
9 | CLI = "cli"
10 |
11 | def __str__(self):
12 | return self.value
13 |
--------------------------------------------------------------------------------
/nest/cli/templates/abstract_empty_template.py:
--------------------------------------------------------------------------------
1 | from nest.core.templates.abstract_base_template import AbstractBaseTemplate
2 |
3 |
4 | class AbstractEmptyTemplate(AbstractBaseTemplate):
5 | def __init__(self):
6 | super().__init__(is_empty=True)
7 |
8 | def generate_app_file(self) -> str:
9 | return f"""from nest.core.app import App
10 | from src.examples.examples_module import ExamplesModule
11 |
12 | app = App(
13 | description="Blank PyNest service",
14 | modules=[
15 | ExamplesModule,
16 | ]
17 | )
18 | """
19 |
20 | def generate_controller_file(self, name) -> str:
21 | return f"""from nest.core import Controller, Get, Post, Depends, Delete, Put
22 |
23 | from src.{name}.{name}_service import {self.capitalized_name}Service
24 | from src.{name}.{name}_model import {self.capitalized_name}
25 |
26 |
27 | @Controller("{name}", tag="{name}")
28 | class {self.capitalized_name}Controller:
29 |
30 | service: {self.capitalized_name}Service = Depends({self.capitalized_name}Service)
31 |
32 | @Get("/get_{name}")
33 | {self.is_async}def get_{name}(self):
34 | return {self.is_await}self.service.get_{name}()
35 | """
36 |
--------------------------------------------------------------------------------
/nest/cli/templates/blank_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 | from pathlib import Path
3 |
4 | from nest.cli.templates.base_template import BaseTemplate
5 |
6 |
7 | class BlankTemplate(BaseTemplate, ABC):
8 | def __init__(self, module_name: str):
9 | super().__init__(module_name)
10 |
11 | def app_file(self):
12 | return f"""from nest.core import PyNestFactory, Module
13 |
14 | from .app_controller import AppController
15 | from .app_service import AppService
16 |
17 |
18 | @Module(imports=[], controllers=[AppController], providers=[AppService])
19 | class AppModule:
20 | pass
21 |
22 |
23 | app = PyNestFactory.create(
24 | AppModule,
25 | description="This is my PyNest app.",
26 | title="PyNest Application",
27 | version="1.0.0",
28 | debug=True,
29 | )
30 |
31 | http_server = app.get_server()
32 |
33 | """
34 |
35 | def config_file(self):
36 | pass
37 |
38 | def docker_file(self):
39 | pass
40 |
41 | def dockerignore_file(self):
42 | pass
43 |
44 | def gitignore_file(self):
45 | pass
46 |
47 | def model_file(self):
48 | return f"""from pydantic import BaseModel
49 |
50 |
51 | class {self.capitalized_module_name}(BaseModel):
52 | name: str
53 |
54 | """
55 |
56 | def service_file(self):
57 | return f"""from .{self.module_name}_model import {self.capitalized_module_name}
58 | from nest.core import Injectable
59 |
60 |
61 | @Injectable
62 | class {self.capitalized_module_name}Service:
63 |
64 | def __init__(self):
65 | self.database = []
66 |
67 | def get_{self.module_name}(self):
68 | return self.database
69 |
70 | def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}):
71 | self.database.append({self.module_name})
72 | return {self.module_name}
73 | """
74 |
75 | def controller_file(self):
76 | return f"""from nest.core import Controller, Get, Post
77 | from .{self.module_name}_service import {self.capitalized_module_name}Service
78 | from .{self.module_name}_model import {self.capitalized_module_name}
79 |
80 |
81 | @Controller("{self.module_name}", tag="{self.module_name}")
82 | class {self.capitalized_module_name}Controller:
83 |
84 | def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service):
85 | self.{self.module_name}_service = {self.module_name}_service
86 |
87 | @Get("/")
88 | def get_{self.module_name}(self):
89 | return self.{self.module_name}_service.get_{self.module_name}()
90 |
91 | @Post("/")
92 | def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}):
93 | return self.{self.module_name}_service.add_{self.module_name}({self.module_name})
94 |
95 | """
96 |
97 | def entity_file(self):
98 | pass
99 |
100 | def create_module(self, module_name: str, src_path: Path):
101 | module_path = src_path / module_name
102 | self.create_folder(module_path)
103 | self.create_template(module_path / "__init__.py", "")
104 | self.create_template(
105 | module_path / f"{module_name}_module.py", self.module_file()
106 | )
107 | self.create_template(
108 | module_path / f"{module_name}_controller.py", self.controller_file()
109 | )
110 | self.create_template(
111 | module_path / f"{module_name}_service.py", self.service_file()
112 | )
113 | self.create_template(module_path / f"{module_name}_model.py", self.model_file())
114 | self.append_module_to_app(path_to_app_py=f"{src_path / 'app_module.py'}")
115 |
116 | def generate_module(self, module_name: str, path: str = None):
117 | src_path = self.validate_new_module(module_name)
118 | self.create_module(module_name, src_path)
119 |
120 | def generate_project(self, project_name: str):
121 | self.create_template(self.nest_path / "settings.yaml", self.settings_file())
122 | root = self.base_path / project_name
123 | src_path = root / "src"
124 | self.create_folder(root)
125 | self.create_template(root / "main.py", self.main_file())
126 | self.create_template(root / "README.md", self.readme_file())
127 | self.create_template(root / "requirements.txt", self.requirements_file())
128 | self.create_folder(src_path)
129 | self.create_template(src_path / "__init__.py", "")
130 | self.create_template(src_path / "app_module.py", self.app_file())
131 | self.create_template(src_path / "app_controller.py", self.app_controller_file())
132 | self.create_template(src_path / "app_service.py", self.app_service_file())
133 |
134 | def settings_file(self):
135 | return f"""# This file is used to configure the nest cli.
136 | config:
137 | db_type: null
138 | is_async: false
139 | """
140 |
141 | def requirements_file(self):
142 | return f"""pynest-api"""
143 |
--------------------------------------------------------------------------------
/nest/cli/templates/cli_templates.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from nest.cli.templates.base_template import BaseTemplate
4 | from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
5 |
6 |
7 | class ClickTemplate(BaseTemplate):
8 | def __init__(self, module_name: str):
9 | super().__init__(module_name)
10 |
11 | def app_file(self):
12 | return f"""from nest.core.pynest_factory import PyNestFactory
13 | from nest.core.cli_factory import CLIAppFactory
14 | from nest.core import Module
15 | from src.app_service import AppService
16 | from src.app_controller import AppController
17 |
18 |
19 | @Module(
20 | imports=[],
21 | controllers=[AppController],
22 | providers=[AppService],
23 | )
24 | class AppModule:
25 | pass
26 |
27 |
28 | cli_app = CLIAppFactory().create(AppModule)
29 |
30 | if __name__ == "__main__":
31 | cli_app()
32 | """
33 |
34 | def module_file(self):
35 | return f"""from nest.core import Module
36 | from src.{self.module_name}.{self.module_name}_controller import {self.module_name.capitalize()}Controller
37 | from src.{self.module_name}.{self.module_name}_service import {self.module_name.capitalize()}Service
38 |
39 |
40 | @Module(
41 | controllers=[{self.module_name.capitalize()}Controller],
42 | providers=[{self.module_name.capitalize()}Service],
43 | )
44 | class {self.module_name.capitalize()}Module:
45 | pass
46 | """
47 |
48 | def controller_file(self):
49 | return f"""from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
50 | from src.{self.module_name}.{self.module_name}_service import {self.module_name.capitalize()}Service
51 |
52 | @CliController("{self.module_name}")
53 | class {self.module_name.capitalize()}Controller:
54 | def __init__(self, {self.module_name}_service: {self.module_name.capitalize()}Service):
55 | self.{self.module_name}_service = {self.module_name}_service
56 |
57 | @CliCommand("hello")
58 | def hello(self):
59 | self.{self.module_name}_service.hello()
60 | """
61 |
62 | def service_file(self):
63 | return f"""from nest.core import Injectable
64 |
65 | @Injectable
66 | class {self.module_name.capitalize()}Service:
67 |
68 |
69 | def hello(self):
70 | print("Hello from {self.module_name.capitalize()}Service")
71 | """
72 |
73 | def settings_file(self):
74 | return f"""config:
75 | db_type: ''
76 | is_async: False
77 | is_cli: True
78 | """
79 |
80 | def app_controller_file(self):
81 | return f"""from nest.core.decorators.cli.cli_decorators import CliCommand, CliController
82 | from src.app_service import AppService
83 |
84 |
85 | @CliController("app")
86 | class AppController:
87 | def __init__(self, app_service: AppService):
88 | self.app_service = app_service
89 |
90 | @CliCommand("version")
91 | def version(self):
92 | self.app_service.version()
93 |
94 | @CliCommand("info")
95 | def info(self):
96 | self.app_service.info()
97 | """
98 |
99 | def app_service_file(self):
100 | return f"""from nest.core import Injectable
101 | import click
102 |
103 |
104 | @Injectable
105 | class AppService:
106 |
107 | def version(self):
108 | print(click.style("1.0.0", fg="blue"))
109 |
110 | def info(self):
111 | print(click.style("This is a cli nest app!", fg="green"))
112 | """
113 |
114 | def create_module(self, module_name: str, src_path: Path):
115 | module_path = src_path / module_name
116 | self.create_folder(module_path)
117 | self.create_template(module_path / "__init__.py", "")
118 | self.create_template(
119 | module_path / f"{module_name}_module.py", self.module_file()
120 | )
121 | self.create_template(
122 | module_path / f"{module_name}_controller.py", self.controller_file()
123 | )
124 | self.create_template(
125 | module_path / f"{module_name}_service.py", self.service_file()
126 | )
127 | self.append_module_to_app(path_to_app_py=f"{src_path / 'app_module.py'}")
128 |
129 | def generate_module(self, module_name: str, path: str = None):
130 | src_path = self.validate_new_module(module_name)
131 | self.create_module(module_name, src_path)
132 |
133 | def generate_project(self, project_name: str):
134 | self.create_template(self.nest_path / "settings.yaml", self.settings_file())
135 | root = self.base_path / project_name
136 | src_path = root / "src"
137 | self.create_folder(root)
138 | self.create_template(root / "README.md", self.readme_file())
139 | self.create_template(root / "requirements.txt", self.requirements_file())
140 | self.create_folder(src_path)
141 | self.create_template(src_path / "__init__.py", "")
142 | self.create_template(src_path / "app_module.py", self.app_file())
143 | self.create_template(src_path / "app_controller.py", self.app_controller_file())
144 | self.create_template(src_path / "app_service.py", self.app_service_file())
145 |
146 | def requirements_file(self):
147 | return f"""pynest-api"""
148 |
149 | def config_file(self):
150 | return ""
151 |
152 | def docker_file(self):
153 | return ""
154 |
155 | def dockerignore_file(self):
156 | return ""
157 |
158 | def entity_file(self):
159 | return ""
160 |
161 | def gitignore_file(self):
162 | return ""
163 |
164 | def model_file(self):
165 | return ""
166 |
--------------------------------------------------------------------------------
/nest/cli/templates/mongo_db_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from nest.cli.templates.abstract_base_template import AbstractBaseTemplate
4 |
5 |
6 | class MongoDbTemplate(AbstractBaseTemplate, ABC):
7 | def __init__(self, name: str):
8 | self.name = name
9 | self.db_type = "mongodb"
10 | super().__init__(self.name, self.db_type)
11 |
12 | def generate_service_file(self) -> str:
13 | return f"""from src.{self.name}.{self.name}_model import {self.capitalized_name}
14 | from src.{self.name}.{self.name}_entity import {self.capitalized_name} as {self.capitalized_name}Entity
15 | from nest.core.decorators import db_request_handler
16 | from functools import lru_cache
17 |
18 |
19 | @lru_cache()
20 | class {self.capitalized_name}Service:
21 |
22 | @db_request_handler
23 | async def get_{self.name}(self):
24 | return await {self.capitalized_name}Entity.find_all().to_list()
25 |
26 | @db_request_handler
27 | async def add_{self.name}(self, {self.name}: {self.capitalized_name}):
28 | new_{self.name} = {self.capitalized_name}Entity(
29 | **{self.name}.dict()
30 | )
31 | await new_{self.name}.save()
32 | return new_{self.name}.id
33 |
34 |
35 | @db_request_handler
36 | async def update_{self.name}(self, {self.name}: {self.capitalized_name}):
37 | return await {self.capitalized_name}Entity.find_one_and_update(
38 | {{"id": {self.name}.id}}, {self.name}.dict()
39 | )
40 |
41 | @db_request_handler
42 | async def delete_{self.name}(self, {self.name}: {self.capitalized_name}):
43 | return await {self.capitalized_name}Entity.find_one_and_delete(
44 | {{"id": {self.name}.id}}
45 | )
46 | """
47 |
48 | def generate_orm_config_file(self) -> str:
49 | return f"""from nest.core.database.base_odm import OdmService
50 | from src.examples.examples_entity import Examples
51 | import os
52 | from dotenv import load_dotenv
53 |
54 | load_dotenv()
55 |
56 | config = OdmService(
57 | db_type="{self.db_type}",
58 | config_params={{
59 | "db_name": os.getenv("DB_NAME"),
60 | "host": os.getenv("DB_HOST"),
61 | "user": os.getenv("DB_USER"),
62 | "password": os.getenv("DB_PASSWORD"),
63 | "port": os.getenv("DB_PORT"),
64 | }},
65 | document_models=[Examples]
66 | )
67 | """
68 |
69 | def generate_entity_file(self) -> str:
70 | return f"""from beanie import Document
71 |
72 |
73 | class {self.capitalized_name}(Document):
74 | name: str
75 |
76 | class Config:
77 | schema_extra = {{
78 | "example": {{
79 | "name": "Example Name",
80 | }}
81 | }}
82 | """
83 |
84 | def generate_requirements_file(self) -> str:
85 | return f"""
86 | pynest-api
87 | """
88 |
89 | def generate_dockerfile(self) -> str:
90 | pass
91 |
--------------------------------------------------------------------------------
/nest/cli/templates/mongo_template.py:
--------------------------------------------------------------------------------
1 | import ast
2 | from abc import ABC
3 | from pathlib import Path
4 |
5 | from nest.cli.templates import Database
6 | from nest.cli.templates.orm_template import AsyncORMTemplate
7 |
8 |
9 | class MongoTemplate(AsyncORMTemplate, ABC):
10 | def __init__(self, module_name: str):
11 | super().__init__(
12 | module_name=module_name,
13 | db_type=Database.MONGODB,
14 | )
15 |
16 | def config_file(self):
17 | return f"""import os
18 | from dotenv import load_dotenv
19 | from nest.core.database.odm_provider import OdmProvider
20 |
21 | load_dotenv()
22 |
23 | config = OdmProvider(
24 | config_params={{
25 | "db_name": os.getenv("DB_NAME", "default_nest_db"),
26 | "host": os.getenv("DB_HOST", "localhost"),
27 | "user": os.getenv("DB_USER", "root"),
28 | "password": os.getenv("DB_PASSWORD", "root"),
29 | "port": os.getenv("DB_PORT", 27017),
30 | }},
31 | document_models=[]
32 | )
33 | """
34 |
35 | def requirements_file(self):
36 | return f"""pynest-api
37 | beanie==1.20.0"""
38 |
39 | def docker_file(self):
40 | return ""
41 |
42 | def entity_file(self):
43 | return f"""from beanie import Document
44 |
45 |
46 | class {self.capitalized_module_name}(Document):
47 | name: str
48 |
49 | class Config:
50 | schema_extra = {{
51 | "example": {{
52 | "name": "Example Name",
53 | }}
54 | }}
55 | """
56 |
57 | def controller_file(self):
58 | return f"""from nest.core import Controller, Get, Post
59 |
60 | from .{self.module_name}_service import {self.capitalized_module_name}Service
61 | from .{self.module_name}_model import {self.capitalized_module_name}
62 |
63 |
64 | @Controller("{self.module_name}", tag="{self.module_name}")
65 | class {self.capitalized_module_name}Controller:
66 |
67 | def __init__(self, {self.module_name}_service: {self.capitalized_module_name}Service):
68 | self.service = service
69 |
70 | @Get("/")
71 | async def get_{self.module_name}(self):
72 | return await self.{self.module_name}_service.get_{self.module_name}()
73 |
74 | @Post("/")
75 | async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}):
76 | return await self.{self.module_name}_service.add_{self.module_name}({self.module_name})
77 | """
78 |
79 | def service_file(self):
80 | return f"""from .{self.module_name}_model import {self.capitalized_module_name}
81 | from .{self.module_name}_entity import {self.capitalized_module_name} as {self.capitalized_module_name}Entity
82 | from nest.core.decorators.database import db_request_handler
83 | from nest.core import Injectable
84 |
85 |
86 | @Injectable
87 | class {self.capitalized_module_name}Service:
88 |
89 | @db_request_handler
90 | async def add_{self.module_name}(self, {self.module_name}: {self.capitalized_module_name}):
91 | new_{self.module_name} = {self.capitalized_module_name}Entity(
92 | **{self.module_name}.dict()
93 | )
94 | await new_{self.module_name}.save()
95 | return new_{self.module_name}.id
96 |
97 | @db_request_handler
98 | async def get_{self.module_name}(self):
99 | return await {self.capitalized_module_name}Entity.find_all().to_list()
100 | """
101 |
102 | def add_document_to_odm_config(self, config_file: Path):
103 | tree = self.append_import(
104 | file_path=config_file,
105 | module_path=f"src.{self.module_name}.{self.module_name}_entity",
106 | class_name=self.capitalized_module_name,
107 | import_exception="from nest.core.database.odm_provider import OdmProvider",
108 | )
109 | modified = False
110 |
111 | for node in ast.walk(tree):
112 | if (
113 | isinstance(node, ast.Call)
114 | and hasattr(node.func, "id")
115 | and node.func.id == "OdmProvider"
116 | ):
117 | for keyword in node.keywords:
118 | if keyword.arg == "document_models":
119 | if isinstance(keyword.value, ast.List):
120 | # Append to existing list
121 | keyword.value.elts.append(
122 | ast.Name(
123 | id=self.capitalized_module_name, ctx=ast.Load()
124 | )
125 | )
126 | modified = True
127 | break
128 |
129 | if modified:
130 | self.save_file_with_astor(config_file, tree)
131 | self.format_with_black(config_file)
132 |
133 | def generate_module(self, module_name: str, path: str = None):
134 | src_path = self.validate_new_module(module_name)
135 | config_file = self.validate_config_file(src_path)
136 | self.create_module(
137 | src_path=src_path,
138 | module_name=module_name,
139 | )
140 | self.add_document_to_odm_config(config_file)
141 |
--------------------------------------------------------------------------------
/nest/cli/templates/mysql_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from nest.cli.templates import Database
4 | from nest.cli.templates.orm_template import AsyncORMTemplate, ORMTemplate
5 |
6 |
7 | class MySQLTemplate(ORMTemplate, ABC):
8 | def __init__(self, module_name: str):
9 | super().__init__(
10 | module_name=module_name,
11 | db_type=Database.MYSQL,
12 | )
13 |
14 | def config_file(self):
15 | return """from nest.core.database.orm_provider import OrmProvider
16 | import os
17 | from dotenv import load_dotenv
18 |
19 | load_dotenv()
20 |
21 | config = OrmProvider(
22 | db_type="mysql",
23 | config_params=dict(
24 | host=os.getenv("MYSQL_HOST"),
25 | db_name=os.getenv("MYSQL_DB_NAME"),
26 | user=os.getenv("MYSQL_USER"),
27 | password=os.getenv("MYSQL_PASSWORD"),
28 | port=int(os.getenv("MYSQL_PORT")),
29 | )
30 | )
31 | """
32 |
33 | def requirements_file(self):
34 | return f"""pynest-api
35 | mysql-connector-python==8.2.0
36 | """
37 |
38 |
39 | class AsyncMySQLTemplate(AsyncORMTemplate, ABC):
40 | def __init__(self, module_name: str):
41 | super().__init__(
42 | module_name=module_name,
43 | db_type=Database.MYSQL,
44 | )
45 |
46 | def config_file(self):
47 | return """from nest.core.database.orm_provider import AsyncOrmProvider
48 | import os
49 | from dotenv import load_dotenv
50 |
51 | load_dotenv()
52 |
53 | config = AsyncOrmProvider(
54 | db_type="mysql",
55 | config_params=dict(
56 | host=os.getenv("MYSQL_HOST"),
57 | db_name=os.getenv("MYSQL_DB_NAME"),
58 | user=os.getenv("MYSQL_USER"),
59 | password=os.getenv("MYSQL_PASSWORD"),
60 | port=int(os.getenv("MYSQL_PORT")),
61 | )
62 | )
63 | """
64 |
65 | def requirements_file(self):
66 | return f"""pynest-api
67 | aiomysql==0.2.0
68 | """
69 |
--------------------------------------------------------------------------------
/nest/cli/templates/postgres_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from nest.cli.templates import Database
4 | from nest.cli.templates.orm_template import AsyncORMTemplate, ORMTemplate
5 |
6 |
7 | class PostgresqlTemplate(ORMTemplate, ABC):
8 | def __init__(self, module_name: str):
9 | super().__init__(
10 | module_name=module_name,
11 | db_type=Database.POSTGRESQL,
12 | )
13 |
14 | def config_file(self):
15 | return """from nest.core.database.orm_provider import OrmProvider
16 | import os
17 | from dotenv import load_dotenv
18 |
19 | load_dotenv()
20 |
21 | config = OrmProvider(
22 | db_type="postgresql",
23 | config_params=dict(
24 | host=os.getenv("POSTGRESQL_HOST", "localhost"),
25 | db_name=os.getenv("POSTGRESQL_DB_NAME", "default_nest_db"),
26 | user=os.getenv("POSTGRESQL_USER", "postgres"),
27 | password=os.getenv("POSTGRESQL_PASSWORD", "postgres"),
28 | port=int(os.getenv("POSTGRESQL_PORT", 5432)),
29 | )
30 | )
31 | """
32 |
33 | def requirements_file(self):
34 | return f"""pynest-api
35 | psycopg2==2.9.6
36 | """
37 |
38 |
39 | class AsyncPostgresqlTemplate(AsyncORMTemplate, ABC):
40 | def __init__(self, module_name: str):
41 | super().__init__(
42 | module_name=module_name,
43 | db_type=Database.POSTGRESQL,
44 | )
45 |
46 | def config_file(self):
47 | return """from nest.core.database.orm_provider import AsyncOrmProvider
48 | import os
49 | from dotenv import load_dotenv
50 |
51 | load_dotenv()
52 |
53 | config = AsyncOrmProvider(
54 | db_type="postgresql",
55 | config_params=dict(
56 | host=os.getenv("POSTGRESQL_HOST", "localhost"),
57 | db_name=os.getenv("POSTGRESQL_DB_NAME", "default_nest_db"),
58 | user=os.getenv("POSTGRESQL_USER", "postgres"),
59 | password=os.getenv("POSTGRESQL_PASSWORD", "postgres"),
60 | port=int(os.getenv("POSTGRESQL_PORT", 5432)),
61 | )
62 | )
63 | """
64 |
65 | def requirements_file(self):
66 | return f"""pynest-api
67 | asyncpg==0.29.0
68 | """
69 |
--------------------------------------------------------------------------------
/nest/cli/templates/relational_db_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from nest import __version__ as version
4 | from nest.cli.templates.abstract_base_template import AbstractBaseTemplate
5 |
6 |
7 | class RelationalDBTemplate(AbstractBaseTemplate, ABC):
8 | def __init__(self, name, db_type):
9 | super().__init__(name, db_type)
10 |
11 | def generate_service_file(self) -> str:
12 | return f"""from src.{self.name}.{self.name}_model import {self.capitalized_name}
13 | from src.{self.name}.{self.name}_entity import {self.capitalized_name} as {self.capitalized_name}Entity
14 | from orm_config import config
15 | from nest.core.decorators import db_request_handler
16 | from functools import lru_cache
17 |
18 |
19 | @lru_cache()
20 | class {self.capitalized_name}Service:
21 |
22 | def __init__(self):
23 | self.orm_config = config
24 | self.session = self.orm_config.get_db()
25 |
26 | @db_request_handler
27 | def add_{self.name}(self, {self.name}: {self.capitalized_name}):
28 | new_{self.name} = {self.capitalized_name}Entity(
29 | **{self.name}.dict()
30 | )
31 | self.session.add(new_{self.name})
32 | self.session.commit()
33 | return new_{self.name}.id
34 |
35 | @db_request_handler
36 | def get_{self.name}(self):
37 | return self.session.query({self.capitalized_name}Entity).all()
38 |
39 | @db_request_handler
40 | def delete_{self.name}(self, {self.name}_id: int):
41 | self.session.query({self.capitalized_name}Entity).filter_by(id={self.name}_id).delete()
42 | self.session.commit()
43 | return {self.name}_id
44 |
45 | @db_request_handler
46 | def update_{self.name}(self, {self.name}_id: int, {self.name}: {self.capitalized_name}):
47 | self.session.query({self.capitalized_name}Entity).filter_by(id={self.name}_id).update(
48 | {self.name}.dict()
49 | )
50 | self.session.commit()
51 | return {self.name}_id
52 | """
53 |
54 | def generate_entity_file(self) -> str:
55 | return f"""from orm_config import config
56 | from sqlalchemy import Column, Integer, String, Float
57 |
58 |
59 | class {self.capitalized_name}(config.Base):
60 | __tablename__ = "{self.name}"
61 |
62 | id = Column(Integer, primary_key=True, autoincrement=True)
63 | name = Column(String, unique=True)
64 | """
65 |
66 | def generate_requirements_file(self) -> str:
67 | return f"""anyio==3.6.2
68 | click==8.1.3
69 | fastapi==0.95.1
70 | fastapi-utils==0.2.1
71 | greenlet==2.0.2
72 | h11==0.14.0
73 | idna==3.4
74 | pydantic==1.10.7
75 | python-dotenv==1.0.0
76 | sniffio==1.3.0
77 | SQLAlchemy==1.4.48
78 | starlette==0.26.1
79 | typing_extensions==4.5.0
80 | uvicorn==0.22.0
81 | pynest-api=={version}
82 | """
83 |
84 | def generate_dockerfile(self) -> str:
85 | pass
86 |
87 | def generate_orm_config_file(self) -> str:
88 | base_template = f"""from nest.core.database.base_orm import OrmService
89 | import os
90 | from dotenv import load_dotenv
91 |
92 | load_dotenv()
93 |
94 |
95 | """
96 |
97 | if self.db_type == "sqlite":
98 | return f"""{base_template}
99 | config = OrmService(
100 | db_type="{self.db_type}",
101 | config_params=dict(
102 | db_name=os.getenv("SQLITE_DB_NAME", "{self.name}_db"),
103 | )
104 | )
105 | """
106 | else:
107 | return f"""{base_template}
108 | config = OrmService(
109 | db_type="{self.db_type}",
110 | config_params=dict(
111 | host=os.getenv("{self.db_type.upper()}_HOST"),
112 | db_name=os.getenv("{self.db_type.upper()}_DB_NAME"),
113 | user=os.getenv("{self.db_type.upper()}_USER"),
114 | password=os.getenv("{self.db_type.upper()}_PASSWORD"),
115 | port=int(os.getenv("{self.db_type.upper()}_PORT")),
116 | )
117 | )
118 | """
119 |
120 |
121 | if __name__ == "__main__":
122 | relational_db_template = RelationalDBTemplate("users", "mysql")
123 | print(relational_db_template.generate_orm_config_file())
124 |
--------------------------------------------------------------------------------
/nest/cli/templates/sqlite_template.py:
--------------------------------------------------------------------------------
1 | from abc import ABC
2 |
3 | from nest.cli.templates import Database
4 | from nest.cli.templates.orm_template import AsyncORMTemplate, ORMTemplate
5 |
6 |
7 | class SQLiteTemplate(ORMTemplate, ABC):
8 | def __init__(self, module_name: str):
9 | super().__init__(
10 | module_name=module_name,
11 | db_type=Database.SQLITE,
12 | )
13 |
14 | def config_file(self):
15 | return """from nest.core.database.orm_provider import OrmProvider
16 | import os
17 | from dotenv import load_dotenv
18 |
19 | load_dotenv()
20 |
21 | config = OrmProvider(
22 | db_type="sqlite",
23 | config_params=dict(
24 | db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db"),
25 | )
26 | )
27 | """
28 |
29 | def requirements_file(self):
30 | return f"""pynest-api"""
31 |
32 | def docker_file(self):
33 | return """FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11
34 |
35 | COPY ./app /app/app
36 | COPY ./requirements.txt /app/requirements.txt
37 | """
38 |
39 |
40 | class AsyncSQLiteTemplate(AsyncORMTemplate, ABC):
41 | def __init__(self, module_name: str):
42 | super().__init__(
43 | module_name=module_name,
44 | db_type=Database.SQLITE,
45 | )
46 |
47 | def config_file(self):
48 | return """from nest.core.database.orm_provider import AsyncOrmProvider
49 | import os
50 | from dotenv import load_dotenv
51 |
52 | load_dotenv()
53 |
54 | config = AsyncOrmProvider(
55 | db_type="sqlite",
56 | config_params=dict(
57 | db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db"),
58 | )
59 | )
60 | """
61 |
62 | def requirements_file(self):
63 | return f"""pynest-api
64 | aiosqlite==0.19.0"""
65 |
66 | def docker_file(self):
67 | return """FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11
68 |
69 | COPY ./app /app/app
70 | COPY ./requirements.txt /app/requirements.txt
71 | """
72 |
--------------------------------------------------------------------------------
/nest/cli/templates/templates_factory.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Union
2 |
3 | from nest.cli.templates import Database
4 | from nest.cli.templates.base_template import BaseTemplate
5 | from nest.cli.templates.blank_template import BlankTemplate
6 | from nest.cli.templates.cli_templates import ClickTemplate
7 | from nest.cli.templates.mongo_template import MongoTemplate
8 | from nest.cli.templates.mysql_template import AsyncMySQLTemplate, MySQLTemplate
9 | from nest.cli.templates.postgres_template import (
10 | AsyncPostgresqlTemplate,
11 | PostgresqlTemplate,
12 | )
13 | from nest.cli.templates.sqlite_template import AsyncSQLiteTemplate, SQLiteTemplate
14 |
15 |
16 | class TemplateFactory:
17 | @staticmethod
18 | def get_template(
19 | db_type: Union[Database, str, None],
20 | module_name: str,
21 | is_async: Optional[bool] = False,
22 | is_cli: Optional[bool] = False,
23 | ) -> BaseTemplate:
24 | if is_cli:
25 | return ClickTemplate(module_name=module_name)
26 | if not db_type:
27 | return BlankTemplate(module_name=module_name)
28 | elif db_type == Database.POSTGRESQL.value:
29 | if is_async:
30 | return AsyncPostgresqlTemplate(module_name=module_name)
31 | else:
32 | return PostgresqlTemplate(module_name=module_name)
33 | elif db_type == Database.MYSQL.value:
34 | if is_async:
35 | return AsyncMySQLTemplate(module_name=module_name)
36 | else:
37 | return MySQLTemplate(module_name=module_name)
38 | elif db_type == Database.SQLITE.value:
39 | if is_async:
40 | return AsyncSQLiteTemplate(module_name=module_name)
41 | else:
42 | return SQLiteTemplate(module_name=module_name)
43 | elif db_type == Database.MONGODB.value:
44 | return MongoTemplate(module_name=module_name)
45 | else:
46 | raise ValueError(f"Unknown database type: {db_type}")
47 |
--------------------------------------------------------------------------------
/nest/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/common/__init__.py
--------------------------------------------------------------------------------
/nest/common/constants.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class ModuleMetadata(str, Enum):
5 | CONTROLLERS = "controllers"
6 | IMPORTS = "imports"
7 | PROVIDERS = "providers"
8 | EXPORTS = "exports"
9 |
10 | def __str__(self):
11 | return self.value
12 |
13 |
14 | INJECTABLE_TOKEN = "__injectable__"
15 | INJECTABLE_NAME = "__injectable__name__"
16 | STATUS_CODE_TOKEN = "status_code"
17 | DEPENDENCIES = "__dependencies__"
18 |
--------------------------------------------------------------------------------
/nest/common/exceptions.py:
--------------------------------------------------------------------------------
1 | class CircularDependencyException(Exception):
2 | def __init__(self, message="Circular dependency detected"):
3 | super().__init__(message)
4 |
5 |
6 | class UnknownModuleException(Exception):
7 | pass
8 |
9 |
10 | class NoneInjectableException(Exception):
11 | def __init__(self, message="None Injectable Classe Detected"):
12 | super().__init__(message)
13 |
--------------------------------------------------------------------------------
/nest/common/route_resolver.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, FastAPI
2 |
3 |
4 | class RoutesResolver:
5 | def __init__(self, container, app_ref: FastAPI):
6 | self.container = container
7 | self.app_ref = app_ref
8 |
9 | def register_routes(self):
10 | for module in self.container.modules.values():
11 | for controller in module.controllers.values():
12 | self.register_route(controller)
13 |
14 | def register_route(self, controller):
15 | router: APIRouter = controller.get_router()
16 | self.app_ref.include_router(router)
17 |
--------------------------------------------------------------------------------
/nest/core/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import Depends
2 |
3 | from nest.core.decorators import (
4 | Controller,
5 | Delete,
6 | Get,
7 | HttpCode,
8 | Injectable,
9 | Module,
10 | Patch,
11 | Post,
12 | Put,
13 | )
14 | from nest.core.decorators.guards import BaseGuard, UseGuards
15 | from nest.core.pynest_application import PyNestApp
16 | from nest.core.pynest_container import PyNestContainer
17 | from nest.core.pynest_factory import PyNestFactory
18 |
--------------------------------------------------------------------------------
/nest/core/cli_factory.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import click
4 |
5 | from nest.core.pynest_container import PyNestContainer
6 | from nest.core.pynest_factory import AbstractPyNestFactory, ModuleType
7 |
8 |
9 | class CLIAppFactory(AbstractPyNestFactory):
10 | def __init__(self):
11 | super().__init__()
12 |
13 | def create(self, app_module: ModuleType, **kwargs):
14 | container = PyNestContainer()
15 | container.add_module(app_module)
16 |
17 | cli_app = click.Group("main")
18 | for module in container.modules.values():
19 | for controller in module.controllers.values():
20 | for command in controller._cli_group.commands.values():
21 | original_callback = command.callback
22 | if asyncio.iscoroutinefunction(original_callback):
23 | command.callback = self._run_async(original_callback)
24 | cli_app.add_command(controller._cli_group)
25 | return cli_app
26 |
27 | @staticmethod
28 | def _run_async(coro):
29 | def wrapper(*args, **kwargs):
30 | return asyncio.run(coro(*args, **kwargs))
31 |
32 | return wrapper
33 |
--------------------------------------------------------------------------------
/nest/core/database/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/core/database/__init__.py
--------------------------------------------------------------------------------
/nest/core/database/base_config.py:
--------------------------------------------------------------------------------
1 | from abc import abstractmethod
2 |
3 |
4 | class ConfigFactoryBase:
5 | """
6 | Factory class for retrieving the appropriate ORM configuration based on the database type.
7 |
8 | Args:
9 | db_type (str): The type of database.
10 |
11 | """
12 |
13 | def __init__(self, db_type: str):
14 | """
15 | Initializes the ConfigFactory instance.
16 |
17 | Args:
18 | db_type (str): The type of database.
19 |
20 | """
21 | self.db_type = db_type
22 |
23 | def get_config(self):
24 | """
25 | Returns the appropriate ORM configuration class based on the database type.
26 |
27 | Returns:
28 | class: The ORM configuration class.
29 |
30 | Raises:
31 | Exception: If the database type is not supported.
32 |
33 | """
34 | assert self.db_type, "db_type is required"
35 |
36 |
37 | class BaseConfig:
38 | """
39 | Base abstract class for ODM (Object-Document Mapping) configurations.
40 |
41 | """
42 |
43 | @abstractmethod
44 | def get_engine_url(self) -> str:
45 | """
46 | Returns the engine URL for the ORM.
47 |
48 | Returns:
49 | str: The engine URL.
50 |
51 | """
52 | pass
53 |
54 |
55 | class BaseProvider(BaseConfig):
56 | """
57 | Base class for Objets Mapping providers that implement the BaseConfig interface.
58 |
59 | """
60 |
61 | def __init__(self, host: str, db_name: str, user: str, password: str, port: int):
62 | """
63 | Initializes the BaseOdmProvider instance.
64 |
65 | Args:
66 | host (str): The database host.
67 | db_name (str): The name of the database.
68 | port (int): The database port number.
69 | user (str): The username for database authentication.
70 | password (str): The password for database authentication.
71 |
72 | """
73 | self.host = host
74 | self.db_name = db_name
75 | self.user = user
76 | self.password = password
77 | self.port = port
78 |
79 | def get_engine_url(self) -> str:
80 | """
81 | Returns the engine URL for the ORM.
82 |
83 | Returns:
84 | str: The engine URL.
85 |
86 | """
87 | pass
88 |
--------------------------------------------------------------------------------
/nest/core/database/odm_config.py:
--------------------------------------------------------------------------------
1 | from nest.core.database.base_config import BaseProvider, ConfigFactoryBase
2 | from urllib.parse import urlencode
3 |
4 |
5 | class MongoDBConfig(BaseProvider):
6 | """
7 | ODM configuration for MongoDB.
8 |
9 | Args:
10 | host (str): The database host.
11 | db_name (str): The name of the database.
12 | user (str): The username for database authentication.
13 | password (str): The password for database authentication.
14 | port (int): The database port number.
15 | srv (bool): Whether to use the SRV connection string.
16 | uri (str): Optional pre-built MongoDB URI.
17 | **kwargs: Additional keyword arguments to include in the URI query parameters.
18 |
19 | """
20 |
21 | def __init__(
22 | self,
23 | host: str = None,
24 | db_name: str = None,
25 | user: str = None,
26 | password: str = None,
27 | port: int = 27017,
28 | srv: bool = False,
29 | uri: str = None,
30 | **kwargs,
31 | ):
32 | """
33 | Initializes the MongoDBConfig instance.
34 |
35 | Args:
36 | host (str): The database host.
37 | db_name (str): The name of the database.
38 | user (str): The username for database authentication.
39 | password (str): The password for database authentication.
40 | port (int): The database port number.
41 | srv (bool): Whether to use the SRV connection string.
42 | uri (str): Optional pre-built MongoDB URI.
43 | **kwargs: Additional keyword arguments to include in the URI query parameters.
44 |
45 | """
46 | self.srv = srv
47 | self.uri = uri
48 | self.kwargs = kwargs or {}
49 | super().__init__(host, db_name, user, password, port)
50 |
51 | def get_engine_url(self) -> str:
52 | """
53 | Returns the engine URL for the ODM.
54 |
55 | Returns:
56 | str: The engine URL.
57 |
58 | """
59 | if self.uri:
60 | return self.uri
61 |
62 | # Build the base URI
63 | protocol = "mongodb+srv" if self.srv else "mongodb"
64 | credentials = ""
65 | if self.user and self.password:
66 | credentials = f"{self.user}:{self.password}@"
67 | elif self.user:
68 | credentials = f"{self.user}@"
69 |
70 | host_port = self.host or ""
71 | if not self.srv and self.port:
72 | host_port = f"{host_port}:{self.port}"
73 |
74 | db_name = f"/{self.db_name}" if self.db_name else ""
75 |
76 | # Build the query string from kwargs
77 | query = ""
78 | if self.kwargs:
79 | query = "?" + urlencode(self.kwargs)
80 |
81 | # Build the full URI
82 | uri = f"{protocol}://{credentials}{host_port}{db_name}{query}"
83 | return uri
84 |
85 |
86 | class ConfigFactory(ConfigFactoryBase):
87 | """
88 | Factory class for retrieving the appropriate ORM configuration based on the database type.
89 |
90 | Args:
91 | db_type (str): The type of database.
92 |
93 | """
94 |
95 | def __init__(self, db_type: str):
96 | """
97 | Initializes the ConfigFactory instance.
98 |
99 | Args:
100 | db_type (str): The type of database.
101 |
102 | """
103 | super().__init__(db_type)
104 |
105 | def get_config(self):
106 | """
107 | Returns the appropriate ODM configuration class based on the database type.
108 |
109 | Returns:
110 | class: The ODM configuration class.
111 |
112 | Raises:
113 | Exception: If the database type is not supported.
114 |
115 | """
116 | if self.db_type == "mongodb":
117 | return MongoDBConfig
118 | else:
119 | raise Exception(f"Database type {self.db_type} is not supported")
120 |
--------------------------------------------------------------------------------
/nest/core/database/odm_provider.py:
--------------------------------------------------------------------------------
1 | from typing import List, Type
2 |
3 | from beanie import Document, init_beanie
4 | from motor.motor_asyncio import AsyncIOMotorClient
5 |
6 | from nest.core.database.odm_config import ConfigFactory
7 |
8 |
9 | class OdmProvider:
10 | """
11 | Provides an interface for working with an ODM (Object-Document Mapping).
12 |
13 | Args:
14 | db_type (str, optional): The type of database. Defaults to "mongodb".
15 | config_params (dict, optional): Configuration parameters specific to the chosen database type.
16 | Defaults to None.
17 | document_models (List[Type[Document]]): A list of beanie.Document subclasses.
18 |
19 | Attributes:
20 | config: The configuration factory for the chosen database type.
21 | config_url: The URL generated from the database configuration parameters.
22 |
23 | """
24 |
25 | def __init__(
26 | self,
27 | db_type="mongodb",
28 | config_params: dict = None,
29 | document_models: list[Type[Document]] = None,
30 | ):
31 | """
32 | Initializes the OdmProvider instance.
33 |
34 | Args:
35 | db_type (str, optional): The type of database. Defaults to "mongodb".
36 | config_params (dict, optional): Configuration parameters specific to the chosen database type.
37 | Defaults to None.
38 | document_models (List[Type[Document]]): A list of beanie.Document subclasses.
39 | """
40 | self.config_object = ConfigFactory(db_type=db_type).get_config()
41 | self.config = self.config_object(**config_params)
42 | self.config_url = self.config.get_engine_url()
43 | self.document_models = document_models or []
44 |
45 | async def create_all(self):
46 | """
47 | Initializes the Beanie ODM with the provided document models.
48 | """
49 | self.check_document_models()
50 | client = AsyncIOMotorClient(self.config_url)
51 | await init_beanie(
52 | database=client.get_default_database(), document_models=self.document_models
53 | )
54 |
55 | def check_document_models(self):
56 | """
57 | Checks that the document_models argument is a list of beanie.Document subclasses.
58 | """
59 | if not isinstance(self.document_models, list):
60 | raise Exception("document_models should be a list")
61 | for document_model in self.document_models:
62 | if not issubclass(document_model, Document):
63 | raise Exception(
64 | "Each item in document_models should be a subclass of beanie.Document"
65 | )
66 |
--------------------------------------------------------------------------------
/nest/core/database/orm_provider.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from contextlib import asynccontextmanager
3 | from typing import Any, Dict
4 |
5 | from sqlalchemy import create_engine
6 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
7 | from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
8 |
9 | from nest.core.database.orm_config import AsyncConfigFactory, ConfigFactory
10 |
11 |
12 | class Base(DeclarativeBase):
13 | """
14 | Base class for ORM models.
15 | """
16 |
17 | pass
18 |
19 |
20 | class BaseOrmProvider(ABC):
21 | def __init__(
22 | self,
23 | db_type: str = "postgresql",
24 | config_params: dict = None,
25 | async_mode: bool = False,
26 | **kwargs,
27 | ):
28 | """
29 | Initializes the BaseOrmProvider instance.
30 |
31 | Args:
32 | db_type (str): The type of database. Defaults to "postgresql".
33 | config_params (dict): Configuration parameters for the database.
34 | async_mode (bool): Flag to indicate if the provider is asynchronous.
35 | """
36 | self.Base = Base
37 |
38 | config_factory = AsyncConfigFactory if async_mode else ConfigFactory
39 |
40 | engine_function = create_async_engine if async_mode else create_engine
41 | if "engine_params" in kwargs:
42 | engine_params: Dict[str, Any] = kwargs.pop("engine_params")
43 | else:
44 | engine_params = {}
45 |
46 | session_function = async_sessionmaker if async_mode else sessionmaker
47 | if "session_params" in kwargs:
48 | session_params: Dict[str, Any] = kwargs.pop("session_params")
49 | else:
50 | session_params = {}
51 |
52 | self.config = config_factory(db_type=db_type).get_config()
53 | self.config_url = self.config(**config_params).get_engine_url()
54 | self.engine = engine_function(self.config_url, **engine_params)
55 | self.session = session_function(self.engine, **session_params)
56 |
57 | @abstractmethod
58 | def create_all(self):
59 | pass
60 |
61 | @abstractmethod
62 | def drop_all(self):
63 | pass
64 |
65 | @abstractmethod
66 | def get_db(self):
67 | pass
68 |
69 |
70 | class OrmProvider(BaseOrmProvider):
71 | """
72 | Synchronous ORM provider.
73 | """
74 |
75 | def __init__(self, db_type: str = "postgresql", config_params: dict = None):
76 | super().__init__(db_type=db_type, config_params=config_params)
77 |
78 | def create_all(self):
79 | self.Base.metadata.create_all(bind=self.engine)
80 |
81 | def drop_all(self):
82 | self.Base.metadata.drop_all(bind=self.engine)
83 |
84 | def get_db(self) -> Session:
85 | db = self.session()
86 | try:
87 | return db
88 | except Exception as e:
89 | raise e
90 | finally:
91 | db.close()
92 |
93 |
94 | class AsyncOrmProvider(BaseOrmProvider):
95 | """
96 | Asynchronous ORM provider.
97 | """
98 |
99 | def __init__(
100 | self, db_type: str = "postgresql", config_params: dict = None, **kwargs
101 | ):
102 | echo = kwargs.get("echo", True)
103 | kwargs["engine_params"] = dict(echo=echo)
104 | kwargs["session_params"] = dict(expire_on_commit=False, class_=AsyncSession)
105 | super().__init__(
106 | db_type=db_type, config_params=config_params, async_mode=True, **kwargs
107 | )
108 |
109 | async def create_all(self):
110 | async with self.engine.begin() as conn:
111 | await conn.run_sync(self.Base.metadata.create_all)
112 |
113 | async def drop_all(self):
114 | async with self.engine.begin() as conn:
115 | await conn.run_sync(self.Base.metadata.drop_all)
116 |
117 | async def get_db(self) -> AsyncSession:
118 | db = self.session()
119 | try:
120 | yield db
121 | finally:
122 | await db.close()
123 |
124 | @asynccontextmanager
125 | async def get_session(self) -> AsyncSession:
126 | db = self.session()
127 | try:
128 | yield db
129 | finally:
130 | await db.close()
131 |
--------------------------------------------------------------------------------
/nest/core/decorators/__init__.py:
--------------------------------------------------------------------------------
1 | from nest.core.decorators.controller import Controller
2 | from nest.core.decorators.http_code import HttpCode
3 | from nest.core.decorators.http_method import Delete, Get, Patch, Post, Put
4 | from nest.core.decorators.injectable import Injectable
5 | from nest.core.decorators.module import Module
6 |
--------------------------------------------------------------------------------
/nest/core/decorators/class_based_view.py:
--------------------------------------------------------------------------------
1 | """
2 | Credit: FastAPI-Utils
3 | Source: https://github.com/dmontagu/fastapi-utils/blob/master/fastapi_utils/cbv.py
4 | """
5 |
6 | import inspect
7 | from typing import (
8 | Any,
9 | Callable,
10 | ClassVar,
11 | List,
12 | Type,
13 | TypeVar,
14 | Union,
15 | get_origin,
16 | get_type_hints,
17 | )
18 |
19 | from fastapi import APIRouter, Depends
20 | from starlette.routing import Route, WebSocketRoute
21 |
22 | T = TypeVar("T")
23 | K = TypeVar("K", bound=Callable[..., Any])
24 |
25 | CBV_CLASS_KEY = "__cbv_class__"
26 |
27 |
28 | def class_based_view(router: APIRouter, cls: Type[T]) -> Type[T]:
29 | """
30 | Replaces any methods of the provided class `cls` that are endpoints of routes in `router` with updated
31 | function calls that will properly inject an instance of `cls`.
32 | """
33 | _init_cbv(cls)
34 | cbv_router = APIRouter()
35 | function_members = inspect.getmembers(cls, inspect.isfunction)
36 | functions_set = set(func for _, func in function_members)
37 | cbv_routes = [
38 | route
39 | for route in router.routes
40 | if isinstance(route, (Route, WebSocketRoute))
41 | and route.endpoint in functions_set
42 | ]
43 | for route in cbv_routes:
44 | router.routes.remove(route)
45 | _update_cbv_route_endpoint_signature(cls, route)
46 | cbv_router.routes.append(route)
47 | router.include_router(cbv_router)
48 | return cls
49 |
50 |
51 | def _init_cbv(cls: Type[Any]) -> None:
52 | """
53 | Idempotently modifies the provided `cls`, performing the following modifications:
54 | * The `__init__` function is updated to set any class-annotated dependencies as instance attributes
55 | * The `__signature__` attribute is updated to indicate to FastAPI what arguments should be passed to the initializer
56 | """
57 | if getattr(cls, CBV_CLASS_KEY, False): # pragma: no cover
58 | return # Already initialized
59 | old_init: Callable[..., Any] = cls.__init__
60 | old_signature = inspect.signature(old_init)
61 | old_parameters = list(old_signature.parameters.values())[
62 | 1:
63 | ] # drop `self` parameter
64 | new_parameters = [
65 | x
66 | for x in old_parameters
67 | if x.kind
68 | not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
69 | ]
70 | dependency_names: List[str] = []
71 | for name, hint in get_type_hints(cls).items():
72 | if get_origin(hint) is ClassVar:
73 | continue
74 | parameter_kwargs = {"default": getattr(cls, name, Ellipsis)}
75 | dependency_names.append(name)
76 | new_parameters.append(
77 | inspect.Parameter(
78 | name=name,
79 | kind=inspect.Parameter.KEYWORD_ONLY,
80 | annotation=hint,
81 | **parameter_kwargs,
82 | )
83 | )
84 | new_signature = old_signature.replace(parameters=new_parameters)
85 |
86 | def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
87 | for dep_name in dependency_names:
88 | dep_value = kwargs.pop(dep_name)
89 | setattr(self, dep_name, dep_value)
90 | old_init(self, *args, **kwargs)
91 |
92 | setattr(cls, "__signature__", new_signature)
93 | setattr(cls, "__init__", new_init)
94 | setattr(cls, CBV_CLASS_KEY, True)
95 |
96 |
97 | def _update_cbv_route_endpoint_signature(
98 | cls: Type[Any], route: Union[Route, WebSocketRoute]
99 | ) -> None:
100 | """
101 | Fixes the endpoint signature for a cbv route to ensure FastAPI performs dependency injection properly.
102 | """
103 | old_endpoint = route.endpoint
104 | old_signature = inspect.signature(old_endpoint)
105 | old_parameters: List[inspect.Parameter] = list(old_signature.parameters.values())
106 | old_first_parameter = old_parameters[0]
107 | new_first_parameter = old_first_parameter.replace(default=Depends(cls))
108 | new_parameters = [new_first_parameter] + [
109 | parameter.replace(kind=inspect.Parameter.KEYWORD_ONLY)
110 | for parameter in old_parameters[1:]
111 | ]
112 | new_signature = old_signature.replace(parameters=new_parameters)
113 | setattr(route.endpoint, "__signature__", new_signature)
--------------------------------------------------------------------------------
/nest/core/decorators/cli/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/nest/core/decorators/cli/__init__.py
--------------------------------------------------------------------------------
/nest/core/decorators/cli/cli_decorators.py:
--------------------------------------------------------------------------------
1 | from functools import partial
2 |
3 | import click
4 |
5 | from nest.core import Controller
6 | from nest.core.decorators.utils import (
7 | get_instance_variables,
8 | parse_dependencies,
9 | parse_params,
10 | )
11 |
12 |
13 | def CliController(name: str, **kwargs):
14 | def decorator(cls):
15 | dependencies = parse_dependencies(cls)
16 | setattr(cls, "__dependencies__", dependencies)
17 | non_dep = get_instance_variables(cls)
18 | for key, value in non_dep.items():
19 | setattr(cls, key, value)
20 | try:
21 | delattr(cls, "__init__")
22 | except AttributeError:
23 | raise AttributeError("Class must have an __init__ method")
24 |
25 | cli_group = click.Group(name, **kwargs)
26 | setattr(cls, "_cli_group", cli_group)
27 |
28 | for attr_name, method in cls.__dict__.items():
29 | if callable(method) and hasattr(method, "_cli_command"):
30 | params = parse_params(method)
31 | cli_command = click.Command(
32 | name=method._cli_command.name,
33 | callback=partial(method, cls),
34 | params=params,
35 | )
36 | cli_group.add_command(cli_command)
37 |
38 | return cls
39 |
40 | return decorator
41 |
42 |
43 | def CliCommand(name: str, **kwargs):
44 | def decorator(func):
45 | params = parse_params(func)
46 | func._cli_command = click.Command(
47 | name, callback=func, params=params, help=kwargs.get("help", None)
48 | )
49 | return func
50 |
51 | return decorator
52 |
--------------------------------------------------------------------------------
/nest/core/decorators/controller.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Type, List
2 |
3 | from fastapi.routing import APIRouter
4 | from fastapi import Depends
5 |
6 | from nest.core.decorators.class_based_view import class_based_view as ClassBasedView
7 | from nest.core.decorators.http_method import HTTPMethod
8 | from nest.core.decorators.utils import get_instance_variables, parse_dependencies
9 | from nest.core.decorators.guards import BaseGuard
10 |
11 |
12 | def Controller(prefix: Optional[str] = None, tag: Optional[str] = None):
13 | """
14 | Decorator that turns a class into a controller, allowing you to define
15 | routes using FastAPI decorators.
16 |
17 | Args:
18 | prefix (str, optional): The prefix to use for all routes.
19 | tag (str, optional): The tag to use for OpenAPI documentation.
20 |
21 | Returns:
22 | class: The decorated class.
23 | """
24 |
25 | # Default route_prefix to tag_name if route_prefix is not provided
26 | route_prefix = process_prefix(prefix, tag)
27 |
28 | def wrapper(cls: Type) -> Type[ClassBasedView]:
29 | router = APIRouter(tags=[tag] if tag else None)
30 |
31 | # Process class dependencies
32 | process_dependencies(cls)
33 |
34 | # Set instance variables
35 | set_instance_variables(cls)
36 |
37 | # Ensure the class has an __init__ method
38 | ensure_init_method(cls)
39 |
40 | # Add routes to the router
41 | add_routes(cls, router, route_prefix)
42 |
43 | # Add get_router method to the class
44 | cls.get_router = classmethod(lambda cls: router)
45 |
46 | return ClassBasedView(router=router, cls=cls)
47 |
48 | return wrapper
49 |
50 |
51 | def process_prefix(route_prefix: Optional[str], tag_name: Optional[str]) -> str:
52 | """Process and format the prefix."""
53 | if route_prefix is None:
54 | if tag_name is None:
55 | return None
56 | else:
57 | route_prefix = tag_name
58 |
59 | if not route_prefix.startswith("/"):
60 | route_prefix = "/" + route_prefix
61 |
62 | if route_prefix.endswith("/"):
63 | route_prefix = route_prefix.rstrip("/")
64 |
65 | return route_prefix
66 |
67 |
68 | def process_dependencies(cls: Type) -> None:
69 | """Parse and set dependencies for the class."""
70 | dependencies = parse_dependencies(cls)
71 | setattr(cls, "__dependencies__", dependencies)
72 |
73 |
74 | def set_instance_variables(cls: Type) -> None:
75 | """Set instance variables for the class."""
76 | non_dependency_vars = get_instance_variables(cls)
77 | for key, value in non_dependency_vars.items():
78 | setattr(cls, key, value)
79 |
80 |
81 | def ensure_init_method(cls: Type) -> None:
82 | """Ensure the class has an __init__ method."""
83 | if not hasattr(cls, "__init__"):
84 | raise AttributeError("Class must have an __init__ method")
85 | try:
86 | delattr(cls, "__init__")
87 | except AttributeError:
88 | pass
89 |
90 |
91 | def add_routes(cls: Type, router: APIRouter, route_prefix: str) -> None:
92 | """Add routes from class methods to the router."""
93 | for method_name, method_function in cls.__dict__.items():
94 | if callable(method_function) and hasattr(method_function, "__http_method__"):
95 | validate_method_decorator(method_function, method_name)
96 | configure_method_route(method_function, route_prefix)
97 | add_route_to_router(router, method_function, cls)
98 |
99 |
100 | def validate_method_decorator(method_function: callable, method_name: str) -> None:
101 | """Validate that the method has a proper HTTP method decorator."""
102 | if (
103 | not hasattr(method_function, "__route_path__")
104 | or not method_function.__route_path__
105 | ):
106 | raise AssertionError(f"Missing path for method {method_name}")
107 |
108 | if not isinstance(method_function.__http_method__, HTTPMethod):
109 | raise AssertionError(f"Invalid method {method_function.__http_method__}")
110 |
111 |
112 | def configure_method_route(method_function: callable, route_prefix: str) -> None:
113 | """Configure the route for the method."""
114 | if not method_function.__route_path__.startswith("/"):
115 | method_function.__route_path__ = "/" + method_function.__route_path__
116 |
117 | method_function.__route_path__ = (
118 | route_prefix + method_function.__route_path__
119 | if route_prefix
120 | else method_function.__route_path__
121 | )
122 |
123 | # remove trailing "/" fro __route_path__
124 | # it converts "/api/users/" to "/api/users"
125 | if (
126 | method_function.__route_path__ != "/"
127 | and method_function.__route_path__.endswith("/")
128 | ):
129 | method_function.__route_path__ = method_function.__route_path__.rstrip("/")
130 |
131 |
132 | def _collect_guards(cls: Type, method: callable) -> List[BaseGuard]:
133 | guards: List[BaseGuard] = []
134 | for guard in getattr(cls, "__guards__", []):
135 | guards.append(guard)
136 | for guard in getattr(method, "__guards__", []):
137 | guards.append(guard)
138 | return guards
139 |
140 |
141 | def add_route_to_router(
142 | router: APIRouter, method_function: callable, cls: Type
143 | ) -> None:
144 | """Add the configured route to the router."""
145 | route_kwargs = {
146 | "path": method_function.__route_path__,
147 | "endpoint": method_function,
148 | "methods": [method_function.__http_method__.value],
149 | **method_function.__kwargs__,
150 | }
151 |
152 | if hasattr(method_function, "status_code"):
153 | route_kwargs["status_code"] = method_function.status_code
154 |
155 | guards = _collect_guards(cls, method_function)
156 | if guards:
157 | dependencies = route_kwargs.get("dependencies", [])
158 | for guard in guards:
159 | dependencies.append(guard.as_dependency())
160 | route_kwargs["dependencies"] = dependencies
161 |
162 | router.add_api_route(**route_kwargs)
163 |
--------------------------------------------------------------------------------
/nest/core/decorators/database.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 |
4 | from fastapi.exceptions import HTTPException
5 | from sqlalchemy.ext.asyncio import AsyncSession
6 |
7 | logging.basicConfig(level=logging.DEBUG)
8 | logger = logging.getLogger(__name__)
9 |
10 |
11 | def db_request_handler(func):
12 | """
13 | Decorator that handles database requests, including error handling and session management.
14 |
15 | Args:
16 | func (function): The function to be decorated.
17 |
18 | Returns:
19 | function: The decorated function.
20 | """
21 |
22 | def wrapper(self, *args, **kwargs):
23 | try:
24 | s = time.time()
25 | result = func(self, *args, **kwargs)
26 | p_time = time.time() - s
27 | logging.info(f"request finished after {p_time}")
28 | if hasattr(self, "session"):
29 | # Check if self is an instance of OrmService
30 | self.session.close()
31 | return result
32 | except Exception as e:
33 | logging.error(e)
34 | if hasattr(self, "session"):
35 | # Check if self is an instance of OrmService
36 | self.session.rollback()
37 | self.session.close()
38 | return HTTPException(status_code=500, detail=str(e))
39 |
40 | return wrapper
41 |
42 |
43 | def async_db_request_handler(func):
44 | """
45 | Asynchronous decorator that handles database requests, including error handling,
46 | session management, and logging for async functions.
47 |
48 | Args:
49 | func (function): The async function to be decorated.
50 |
51 | Returns:
52 | function: The decorated async function.
53 | """
54 |
55 | async def wrapper(*args, **kwargs):
56 | try:
57 | start_time = time.time()
58 | result = await func(*args, **kwargs) # Awaiting the async function
59 | process_time = time.time() - start_time
60 | logger.info(f"Async request finished after {process_time} seconds")
61 | return result
62 | except Exception as e:
63 | self = args[0] if args else None
64 | session = getattr(self, "session", None)
65 | # If not found, check in function arguments
66 | if session:
67 | session_type = "class"
68 | else:
69 | session = [arg for arg in args if isinstance(arg, AsyncSession)][0]
70 | if session:
71 | session_type = "function"
72 | else:
73 | raise ValueError("AsyncSession not provided to the function")
74 |
75 | logger.error(f"Error in async request: {e}")
76 | # Rollback if session is in a transaction
77 | if session and session_type == "function" and session.in_transaction():
78 | await session.rollback()
79 | elif session and session_type == "class":
80 | async with session() as session:
81 | await session.rollback()
82 | raise Exception(f"Error in async request: {e}")
83 |
84 | return wrapper
85 |
--------------------------------------------------------------------------------
/nest/core/decorators/http_code.py:
--------------------------------------------------------------------------------
1 | from nest.common.constants import STATUS_CODE_TOKEN
2 |
3 |
4 | def HttpCode(status_code: int):
5 | """
6 | Decorator that sets the HTTP status code for a route.
7 |
8 | Args:
9 | status_code (int): The HTTP status code for the response.
10 | """
11 |
12 | def decorator(func):
13 | if not hasattr(func, STATUS_CODE_TOKEN):
14 | setattr(func, STATUS_CODE_TOKEN, status_code)
15 | return func
16 |
17 | return decorator
18 |
--------------------------------------------------------------------------------
/nest/core/decorators/http_method.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import Any, Callable, List, Union
3 |
4 |
5 | class HTTPMethod(Enum):
6 | GET = "GET"
7 | POST = "POST"
8 | DELETE = "DELETE"
9 | PUT = "PUT"
10 | PATCH = "PATCH"
11 | HEAD = "HEAD"
12 | OPTIONS = "OPTIONS"
13 |
14 |
15 | def route(http_method: HTTPMethod, route_path: Union[str, List[str]] = "/", **kwargs):
16 | """
17 | Decorator that defines a route for the controller.
18 |
19 | Args:
20 | http_method (HTTPMethod): The HTTP method for the route (GET, POST, DELETE, PUT, PATCH).
21 | route_path (Union[str, List[str]]): The route path for the route. example: "/users"
22 | **kwargs: Additional keyword arguments to configure the route.
23 |
24 | Returns:
25 | function: The decorated function.
26 | """
27 |
28 | def decorator(func):
29 | func.__http_method__ = http_method
30 | func.__route_path__ = route_path
31 | func.__kwargs__ = kwargs
32 |
33 | return func
34 |
35 | return decorator
36 |
37 |
38 | def Get(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
39 | return route(HTTPMethod.GET, route_path, **kwargs)
40 |
41 |
42 | def Post(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
43 | return route(HTTPMethod.POST, route_path, **kwargs)
44 |
45 |
46 | def Delete(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
47 | return route(HTTPMethod.DELETE, route_path, **kwargs)
48 |
49 |
50 | def Put(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
51 | return route(HTTPMethod.PUT, route_path, **kwargs)
52 |
53 |
54 | def Patch(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
55 | return route(HTTPMethod.PATCH, route_path, **kwargs)
56 |
57 |
58 | def Head(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
59 | return route(HTTPMethod.HEAD, route_path, **kwargs)
60 |
61 |
62 | def Options(route_path: Union[str, List[str]] = "/", **kwargs) -> Callable[..., Any]:
63 | return route(HTTPMethod.OPTIONS, route_path, **kwargs)
64 |
--------------------------------------------------------------------------------
/nest/core/decorators/injectable.py:
--------------------------------------------------------------------------------
1 | from typing import Callable, Optional, Type
2 |
3 | from injector import inject
4 |
5 | from nest.common.constants import DEPENDENCIES, INJECTABLE_NAME, INJECTABLE_TOKEN
6 | from nest.core.decorators.utils import parse_dependencies
7 |
8 |
9 | def Injectable(target_class: Optional[Type] = None, *args, **kwargs) -> Callable:
10 | """
11 | Decorator to mark a class as injectable and handle its dependencies.
12 |
13 | Args:
14 | target_class (Type, optional): The class to be decorated.
15 |
16 | Returns:
17 | Callable: The decorator function.
18 | """
19 |
20 | def decorator(decorated_class: Type) -> Type:
21 | """
22 | Inner decorator function to process the class.
23 |
24 | Args:
25 | decorated_class (Type): The class to be processed.
26 |
27 | Returns:
28 | Type: The processed class with dependencies injected.
29 | """
30 |
31 | if "__init__" not in decorated_class.__dict__:
32 |
33 | def init_method(self, *args, **kwargs):
34 | pass
35 |
36 | decorated_class.__init__ = init_method
37 |
38 | dependencies = parse_dependencies(decorated_class)
39 |
40 | setattr(decorated_class, DEPENDENCIES, dependencies)
41 | setattr(decorated_class, INJECTABLE_TOKEN, True)
42 | setattr(decorated_class, INJECTABLE_NAME, decorated_class.__name__)
43 |
44 | inject(decorated_class)
45 |
46 | return decorated_class
47 |
48 | if target_class is not None:
49 | return decorator(target_class)
50 |
51 | return decorator
52 |
--------------------------------------------------------------------------------
/nest/core/decorators/module.py:
--------------------------------------------------------------------------------
1 | from nest.common.constants import ModuleMetadata
2 | from typing import List
3 | import dataclasses
4 |
5 |
6 | @dataclasses.dataclass(frozen=True)
7 | class Module:
8 | controllers: List[type] = dataclasses.field(default_factory=list)
9 | providers: List[type] = dataclasses.field(default_factory=list)
10 | exports: List[type] = dataclasses.field(default_factory=list)
11 | imports: List[type] = dataclasses.field(default_factory=list)
12 | is_global: bool = dataclasses.field(default=False)
13 |
14 | def __call__(self, cls):
15 | setattr(cls, ModuleMetadata.CONTROLLERS, self.controllers)
16 | setattr(cls, ModuleMetadata.PROVIDERS, self.providers)
17 | setattr(cls, ModuleMetadata.IMPORTS, self.imports)
18 | setattr(cls, ModuleMetadata.EXPORTS, self.exports)
19 | setattr(cls, "__is_module__", True)
20 | setattr(cls, "__is_global__", self.is_global)
21 |
22 | return cls
23 |
--------------------------------------------------------------------------------
/nest/core/decorators/utils.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import inspect
3 | from typing import Callable, List
4 |
5 | import click
6 |
7 | from nest.common.constants import INJECTABLE_TOKEN
8 |
9 |
10 | def get_instance_variables(cls):
11 | """
12 | Retrieves instance variables assigned in the __init__ method of a class,
13 | excluding those that are injected dependencies.
14 |
15 | Args:
16 | cls (type): The class to inspect.
17 |
18 | Returns:
19 | dict: A dictionary with variable names as keys and their assigned values.
20 | """
21 | try:
22 | source = inspect.getsource(cls.__init__).strip()
23 | tree = ast.parse(source)
24 |
25 | # Getting the parameter names to exclude dependencies
26 | dependencies = set(
27 | param.name
28 | for param in inspect.signature(cls.__init__).parameters.values()
29 | if param.annotation != param.empty
30 | and getattr(param.annotation, "__injectable__", False)
31 | )
32 |
33 | instance_vars = {}
34 | for node in ast.walk(tree):
35 | if isinstance(node, ast.Assign):
36 | for target in node.targets:
37 | if (
38 | isinstance(target, ast.Attribute)
39 | and isinstance(target.value, ast.Name)
40 | and target.value.id == "self"
41 | ):
42 | # Exclude dependencies
43 | if target.attr not in dependencies:
44 | # Here you can either store the source code of the value or
45 | # evaluate it in the class' context, depending on your needs
46 | instance_vars[target.attr] = ast.get_source_segment(
47 | source, node.value
48 | )
49 | return instance_vars
50 | except Exception as e:
51 | return {}
52 |
53 |
54 | def get_non_dependencies_params(cls):
55 | source = inspect.getsource(cls.__init__).strip()
56 | tree = ast.parse(source)
57 | non_dependencies = {}
58 | for node in ast.walk(tree):
59 | if isinstance(node, ast.Attribute):
60 | non_dependencies[node.attr] = node.value.id
61 | return non_dependencies
62 |
63 |
64 | def parse_dependencies(cls):
65 | signature = inspect.signature(cls.__init__)
66 | dependecies = {}
67 | for param in signature.parameters.values():
68 | try:
69 | if (
70 | param.annotation != param.empty
71 | and hasattr(param.annotation, "__dict__")
72 | and INJECTABLE_TOKEN in param.annotation.__dict__
73 | ):
74 | dependecies[param.name] = param.annotation
75 | except Exception as e:
76 | raise e
77 | return dependecies
78 |
79 |
80 | def parse_params(func: Callable) -> List[click.Option]:
81 | signature = inspect.signature(func)
82 | params = []
83 | for param in signature.parameters.values():
84 | try:
85 | if param.annotation != param.empty:
86 | params.append(param.annotation)
87 | except Exception as e:
88 | raise e
89 | return params
90 |
--------------------------------------------------------------------------------
/nest/core/pynest_app_context.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import TypeVar, Union
3 |
4 | from nest.common.exceptions import UnknownModuleException
5 | from nest.common.module import Module, ModuleCompiler
6 | from nest.core.pynest_container import PyNestContainer
7 |
8 | T = TypeVar("T")
9 |
10 |
11 | class PyNestApplicationContext:
12 | """
13 | Represents the application context in a PyNest application.
14 | This class is responsible for managing the application's lifecycle and modules.
15 |
16 | Attributes:
17 | container (PyNestContainer): The container that holds the application's modules and manages the dependency injection.
18 | context_module (Module, optional): The module that represents the current context of the application.
19 | logger (Logger): Logger for the application context.
20 |
21 | Args:
22 | container (PyNestContainer): The container for the application.
23 | context_module (Union[Module, None], optional): The initial context module of the application.
24 | """
25 |
26 | _is_initialized = False
27 | _module_compiler = ModuleCompiler()
28 |
29 | @property
30 | def is_initialized(self):
31 | """
32 | Property to check if the application context is initialized.
33 |
34 | Returns:
35 | bool: True if the application context is initialized, False otherwise.
36 | """
37 | return self._is_initialized
38 |
39 | @is_initialized.setter
40 | def is_initialized(self, value: bool):
41 | """
42 | Setter for the is_initialized property.
43 |
44 | Args:
45 | value (bool): The new value for the is_initialized property.
46 | """
47 | self._is_initialized = value
48 |
49 | def init(self):
50 | """
51 | Initializes the application context. If the context is already initialized, it returns itself.
52 |
53 | Returns:
54 | PyNestApplicationContext: The initialized application context.
55 | """
56 | if self._is_initialized:
57 | return self
58 |
59 | self._is_initialized = True
60 | return self
61 |
62 | def __init__(
63 | self, container: PyNestContainer, context_module: Union[Module, None] = None
64 | ):
65 | """
66 | Constructor for the PyNestApplicationContext.
67 |
68 | Args:
69 | container (PyNestContainer): The container for the application.
70 | context_module (Union[Module, None], optional): The initial context module of the application.
71 | """
72 | self.container = container
73 | self.context_module: Module = context_module
74 | self.logger = logging.getLogger(PyNestApplicationContext.__name__)
75 |
76 | def select_context_module(self):
77 | """
78 | Selects the first module from the container as the context module.
79 | """
80 | modules = self.container.modules.values()
81 | self.context_module = next(iter(modules), None)
82 |
83 | def select(self, module: T) -> T:
84 | """
85 | Selects a specific module as the current context module based on the provided module class - The selected Module is the AppModule that contains all the application's graph.
86 |
87 | Args:
88 | module (Module): The module class to select.
89 |
90 | Returns:
91 | PyNestApplicationContext: A new application context with the selected module.
92 |
93 | Raises:
94 | UnknownModuleException: If the specified module is not found in the application's modules.
95 | """
96 | modules_container = self.container.modules
97 | module_token_factory = self.container.module_token_factory
98 |
99 | metadata = self._module_compiler.extract_metadata(module)
100 | token = module_token_factory.create(metadata["type"])
101 | selected_module = modules_container.get(token)
102 | if selected_module is None:
103 | raise UnknownModuleException()
104 |
105 | return PyNestApplicationContext(self.container, selected_module)
106 |
--------------------------------------------------------------------------------
/nest/core/pynest_application.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from fastapi import FastAPI
4 |
5 | from nest.common.route_resolver import RoutesResolver
6 | from nest.core.pynest_app_context import PyNestApplicationContext
7 | from nest.core.pynest_container import PyNestContainer
8 |
9 |
10 | class PyNestApp(PyNestApplicationContext):
11 | """
12 | PyNestApp is the main application class for the PyNest framework,
13 | managing the container and HTTP server.
14 | """
15 |
16 | _is_listening = False
17 |
18 | @property
19 | def is_listening(self) -> bool:
20 | return self._is_listening
21 |
22 | def __init__(self, container: PyNestContainer, http_server: FastAPI):
23 | """
24 | Initialize the PyNestApp with the given container and HTTP server.
25 |
26 | Args:
27 | container (PyNestContainer): The PyNestContainer container instance.
28 | http_server (FastAPI): The FastAPI server instance.
29 | """
30 | self.container = container
31 | self.http_server = http_server
32 | super().__init__(self.container)
33 | self.routes_resolver = RoutesResolver(self.container, self.http_server)
34 | self.select_context_module()
35 | self.register_routes()
36 |
37 | def use(self, middleware: type, **options: Any) -> "PyNestApp":
38 | """
39 | Add middleware to the FastAPI server.
40 |
41 | Args:
42 | middleware (type): The middleware class.
43 | **options (Any): Additional options for the middleware.
44 |
45 | Returns:
46 | PyNestApp: The current instance of PyNestApp, allowing method chaining.
47 | """
48 | self.http_server.add_middleware(middleware, **options)
49 | return self
50 |
51 | def get_server(self) -> FastAPI:
52 | """
53 | Get the FastAPI server instance.
54 |
55 | Returns:
56 | FastAPI: The FastAPI server instance.
57 | """
58 | return self.http_server
59 |
60 | def register_routes(self):
61 | """
62 | Register the routes using the RoutesResolver.
63 | """
64 | self.routes_resolver.register_routes()
65 |
--------------------------------------------------------------------------------
/nest/core/pynest_factory.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Type, TypeVar
3 |
4 | from fastapi import FastAPI
5 |
6 | from nest.core.pynest_application import PyNestApp
7 | from nest.core.pynest_container import PyNestContainer
8 |
9 | ModuleType = TypeVar("ModuleType")
10 |
11 |
12 | class AbstractPyNestFactory(ABC):
13 | @abstractmethod
14 | def create(self, main_module: Type[ModuleType], **kwargs):
15 | raise NotImplementedError
16 |
17 |
18 | class PyNestFactory(AbstractPyNestFactory):
19 | """Factory class for creating PyNest applications."""
20 |
21 | @staticmethod
22 | def create(main_module: Type[ModuleType], **kwargs) -> PyNestApp:
23 | """
24 | Create a PyNest application with the specified main module class.
25 |
26 | Args:
27 | main_module (ModuleType): The main module for the PyNest application.
28 | **kwargs: Additional keyword arguments for the FastAPI server.
29 |
30 | Returns:
31 | PyNestApp: The created PyNest application.
32 | """
33 | container = PyNestContainer()
34 | container.add_module(main_module)
35 | http_server = PyNestFactory._create_server(**kwargs)
36 | return PyNestApp(container, http_server)
37 |
38 | @staticmethod
39 | def _create_server(**kwargs) -> FastAPI:
40 | """
41 | Create a FastAPI server.
42 |
43 | Args:
44 | **kwargs: Additional keyword arguments for the FastAPI server.
45 |
46 | Returns:
47 | FastAPI: The created FastAPI server.
48 | """
49 | return FastAPI(**kwargs)
50 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["poetry-core"]
3 | build-backend = "poetry.core.masonry.api"
4 |
5 |
6 | [tool.poetry]
7 | name = "pynest-api"
8 | version = "0.4.0"
9 | description = "PyNest is a FastAPI Abstraction for building microservices, influenced by NestJS."
10 | authors = ["itay.dar "]
11 | readme = "README.md"
12 | homepage = "https://github.com/PythonNest/PyNest"
13 | documentation = "https://pythonnest.github.io/PyNest/"
14 | packages = [
15 | { include = "nest" }
16 | ]
17 | classifiers = [
18 | "Programming Language :: Python :: 3",
19 | "Programming Language :: Python :: 3.10",
20 | "Programming Language :: Python :: 3.11",
21 | "Programming Language :: Python :: 3.12",
22 | "Operating System :: OS Independent",
23 | "License :: OSI Approved :: MIT License",
24 | "Development Status :: 4 - Beta",
25 | "Intended Audience :: Developers",
26 | "Topic :: Software Development :: Libraries :: Python Modules",
27 | ]
28 |
29 |
30 |
31 | [tool.poetry.dependencies]
32 | python = "^3.9"
33 | # Core dependencies
34 | click = "^8.1.7"
35 | injector = "^0.22.0"
36 | astor = "^0.8.1"
37 | pyyaml = "^6.0.2"
38 | fastapi = "^0.115.4"
39 | pydantic = "^2.9.2"
40 | uvicorn = "^0.32.0"
41 |
42 |
43 | # Optional dependencies
44 | sqlalchemy = { version = "^2.0.36", optional = true }
45 | asyncpg = { version = "^0.30.0", optional = true }
46 | psycopg2 = { version = "^2.9.3", optional = true }
47 | alembic = { version = "^1.13.3", optional = true }
48 | beanie = { version = "^1.27.0", optional = true }
49 | python-dotenv = { version = "^1.0.1", optional = true }
50 | greenlet = { version = "^3.1.1", optional = true }
51 | black = "^24.10.0"
52 |
53 |
54 |
55 | [tool.poetry.extras]
56 | postgres = ["sqlalchemy", "asyncpg", "psycopg2", "alembic", "greenlet", "python-dotenv"]
57 | mongo = ["beanie", "python-dotenv"]
58 | test = ["pytest"]
59 |
60 | [tool.poetry.group.build.dependencies]
61 | setuptools = "^75.3.0"
62 | wheel = "^0.44.0"
63 | build = "^1.2.2.post1"
64 | twine = "^5.1.1"
65 | git-changelog = "^2.5.2"
66 |
67 | [tool.poetry.group.test.dependencies]
68 | pytest = "^7.0.1"
69 | fastapi = "^0.115.4"
70 | sqlalchemy = "^2.0.36"
71 | motor = "^3.2.0"
72 | beanie = "^1.27.0"
73 | pydantic = "^2.9.2"
74 | python-dotenv = "^1.0.1"
75 | uvicorn = "^0.32.0"
76 |
77 | [tool.poetry.group.docs.dependencies]
78 | mkdocs-material = "^9.5.43"
79 | mkdocstrings-python = "^1.12.2"
80 |
81 |
82 | [tool.black]
83 | force-exclude = '''
84 | /(
85 | | /*venv*
86 | | /.git
87 | | /dist
88 | | /pytest_compare.egg-info
89 | | *.bac
90 | | .mypy_cache
91 | | .coverage
92 | | /htmlcov
93 | | /docs
94 | | /site
95 | )/
96 | '''
97 |
98 | [tool.mypy]
99 | exclude = [
100 | "/*venv*"
101 | ]
102 | ignore_missing_imports = true
103 |
104 | [tool.poetry.urls]
105 | Homepage = "https://github.com/PythonNest/PyNest"
106 | Documentation = "https://pythonnest.github.io/PyNest/"
107 |
108 | [tool.poetry.scripts]
109 | pynest = "nest.cli.cli:nest_cli"
110 |
111 |
112 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_common/__init__.py
--------------------------------------------------------------------------------
/tests/test_common/test_module.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_common/test_module.py
--------------------------------------------------------------------------------
/tests/test_common/test_route_resolver.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_common/test_route_resolver.py
--------------------------------------------------------------------------------
/tests/test_core/__init__.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from fastapi import FastAPI
3 |
4 | from nest.common.route_resolver import RoutesResolver
5 | from nest.core import (
6 | Controller,
7 | Get,
8 | Injectable,
9 | Module,
10 | PyNestContainer,
11 | PyNestFactory,
12 | )
13 | from nest.core.pynest_application import PyNestApp
14 |
15 |
16 | @Injectable
17 | class TestService:
18 | def get_message(self):
19 | return "Hello, World!"
20 |
21 |
22 | @Controller("test")
23 | class TestController:
24 | def __init__(self, test_service: TestService):
25 | self.test_service = test_service
26 |
27 | @Get("/")
28 | def get_test(self):
29 | return {"message": "GET endpoint"}
30 |
31 | @Get("/message")
32 | def get_message(self):
33 | return {"message": self.test_service.get_message()}
34 |
35 |
36 | @Module(controllers=[TestController], providers=[TestService], exports=[TestService])
37 | class TestModule:
38 | pass
39 |
40 |
41 | @pytest.fixture
42 | def test_module():
43 | return TestModule
44 |
45 |
46 | @pytest.fixture
47 | def test_server() -> FastAPI:
48 | server = PyNestFactory._create_server(
49 | title="Test Server",
50 | description="This is a test server",
51 | version="1.0.0",
52 | debug=True,
53 | )
54 | return server
55 |
56 |
57 | @pytest.fixture
58 | def test_container():
59 | return PyNestContainer()
60 |
61 |
62 | @pytest.fixture
63 | def test_resolver(test_container, test_server):
64 | return RoutesResolver(test_container, test_server)
65 |
--------------------------------------------------------------------------------
/tests/test_core/test_app_context.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_core/test_app_context.py
--------------------------------------------------------------------------------
/tests/test_core/test_database/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_core/test_database/__init__.py
--------------------------------------------------------------------------------
/tests/test_core/test_database/test_odm.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pytest
4 |
5 | from nest.core.database.odm_config import ConfigFactory, MongoDBConfig
6 | from nest.core.database.odm_provider import OdmProvider
7 |
8 |
9 | @pytest.fixture(scope="module")
10 | def odm_service():
11 | return OdmProvider(
12 | db_type="mongodb",
13 | config_params={
14 | "db_name": "db_name",
15 | "host": "host",
16 | "user": "user",
17 | "password": "password",
18 | "port": "port",
19 | },
20 | document_models=[],
21 | )
22 |
23 |
24 | @pytest.fixture(scope="module")
25 | def mongodb_config():
26 | return MongoDBConfig(
27 | db_name="db_name", host="host", user="user", password="password", port="port"
28 | )
29 |
30 |
31 | def test_odm_service_definition(odm_service):
32 | assert odm_service.config
33 | assert odm_service.config_url
34 | assert odm_service.document_models == []
35 |
36 |
37 | def test_odm_service_config_url(odm_service):
38 | config_url = odm_service.config_url
39 | assert config_url == "mongodb://user:password@host:port/db_name"
40 |
41 |
42 | def test_mongo_config_definition(mongodb_config):
43 | assert mongodb_config
44 | assert mongodb_config.get_engine_url
45 |
--------------------------------------------------------------------------------
/tests/test_core/test_database/test_orm.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pytest
4 |
5 | from nest.core.database.orm_config import ConfigFactory
6 | from nest.core.database.orm_provider import OrmProvider
7 |
8 |
9 | @pytest.fixture(scope="module")
10 | def config_factory():
11 | return ConfigFactory
12 |
13 |
14 | def test_config_factory_definition(config_factory):
15 | assert config_factory
16 | assert config_factory.get_config
17 |
18 |
19 | @pytest.fixture(scope="module")
20 | def sqlite_config_factory(config_factory):
21 | config = config_factory(db_type="sqlite").get_config()
22 | params = dict(db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db"))
23 | return config(**params)
24 |
25 |
26 | @pytest.fixture(scope="module")
27 | def postgres_config_factory(config_factory):
28 | config = config_factory(db_type="postgresql").get_config()
29 | params = dict(
30 | db_name=os.getenv("POSTGRES_DB_NAME", "default_nest_db"),
31 | host=os.getenv("POSTGRES_HOST", "localhost"),
32 | user=os.getenv("POSTGRES_USER", "postgres"),
33 | password=os.getenv("POSTGRES_PASSWORD", "postgres"),
34 | port=os.getenv("POSTGRES_PORT", "5432"),
35 | )
36 | return config(**params)
37 |
38 |
39 | @pytest.fixture(scope="module")
40 | def mysql_config_factory(config_factory):
41 | config = config_factory(db_type="mysql").get_config()
42 | params = dict(
43 | db_name=os.getenv("MYSQL_DB_NAME", "default_nest_db"),
44 | host=os.getenv("MYSQL_HOST", "localhost"),
45 | user=os.getenv("MYSQL_USER", "root"),
46 | password=os.getenv("MYSQL_PASSWORD", "root"),
47 | port=os.getenv("MYSQL_PORT", "3306"),
48 | )
49 | return config(**params)
50 |
51 |
52 | def test_sqlite_url(sqlite_config_factory):
53 | config_url = sqlite_config_factory.get_engine_url()
54 | assert config_url == "sqlite:///default_nest_db.db"
55 |
56 |
57 | def test_postgres_url(postgres_config_factory):
58 | config_url = postgres_config_factory.get_engine_url()
59 | assert (
60 | config_url
61 | == "postgresql+psycopg2://postgres:postgres@localhost:5432/default_nest_db"
62 | )
63 |
64 |
65 | def test_mysql_url(mysql_config_factory):
66 | config_url = mysql_config_factory.get_engine_url()
67 | assert (
68 | config_url == "mysql+mysqlconnector://root:root@localhost:3306/default_nest_db"
69 | )
70 |
71 |
72 | @pytest.fixture(scope="module")
73 | def sqlite_orm_service():
74 | return OrmProvider(
75 | db_type="sqlite",
76 | config_params=dict(db_name=os.getenv("SQLITE_DB_NAME", "default_nest_db")),
77 | )
78 |
79 |
80 | @pytest.fixture(scope="module")
81 | def postgres_orm_service():
82 | return OrmProvider(
83 | db_type="postgresql",
84 | config_params=dict(
85 | db_name=os.getenv("POSTGRES_DB_NAME", "default_nest_db"),
86 | host=os.getenv("POSTGRES_HOST", "localhost"),
87 | user=os.getenv("POSTGRES_USER", "postgres"),
88 | password=os.getenv("POSTGRES_PASSWORD", "postgres"),
89 | port=os.getenv("POSTGRES_PORT", "5432"),
90 | ),
91 | )
92 |
93 |
94 | @pytest.fixture(scope="module")
95 | def mysql_orm_service():
96 | return OrmProvider(
97 | db_type="mysql",
98 | config_params=dict(
99 | db_name=os.getenv("MYSQL_DB_NAME", "default_nest_db"),
100 | host=os.getenv("MYSQL_HOST", "localhost"),
101 | user=os.getenv("MYSQL_USER", "root"),
102 | password=os.getenv("MYSQL_PASSWORD", "root"),
103 | port=os.getenv("MYSQL_PORT", "3306"),
104 | ),
105 | )
106 |
--------------------------------------------------------------------------------
/tests/test_core/test_decorators/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonNest/PyNest/fb56de203153c271965a3c9ff2da42be03d6fcf0/tests/test_core/test_decorators/__init__.py
--------------------------------------------------------------------------------
/tests/test_core/test_decorators/test_controller.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from nest.core import Controller, Delete, Get, Injectable, Patch, Post, Put
4 |
5 |
6 | @Controller(prefix="api/v1/user", tag="test")
7 | class TestController:
8 | def __init__(self): ...
9 |
10 | @Get("/get_all_users")
11 | def get_endpoint(self):
12 | return {"message": "GET endpoint"}
13 |
14 | @Post("/create_user")
15 | def post_endpoint(self):
16 | return {"message": "POST endpoint"}
17 |
18 | @Delete("/delete_user")
19 | def delete_endpoint(self):
20 | return {"message": "DELETE endpoint"}
21 |
22 | @Put("/update_user")
23 | def put_endpoint(self):
24 | return {"message": "PUT endpoint"}
25 |
26 | @Patch("/patch_user")
27 | def patch_endpoint(self):
28 | return {"message": "PATCH endpoint"}
29 |
30 |
31 | @pytest.fixture
32 | def test_controller():
33 | return TestController()
34 |
35 |
36 | @pytest.mark.parametrize(
37 | "function, endpoint, expected_message",
38 | [
39 | ("get_endpoint", "get_all_users", "GET endpoint"),
40 | ("post_endpoint", "create_user", "POST endpoint"),
41 | ("delete_endpoint", "delete_user", "DELETE endpoint"),
42 | ("put_endpoint", "update_user", "PUT endpoint"),
43 | ("patch_endpoint", "patch_user", "PATCH endpoint"),
44 | ],
45 | )
46 | def test_endpoints(test_controller, function, endpoint, expected_message):
47 | attribute = getattr(test_controller, function)
48 | assert attribute.__route_path__ == "/api/v1/user/" + endpoint
49 | assert attribute.__kwargs__ == {}
50 | assert attribute.__http_method__.value == function.split("_")[0].upper()
51 | assert attribute() == {"message": f"{function.split('_')[0].upper()} endpoint"}
52 |
--------------------------------------------------------------------------------
/tests/test_core/test_decorators/test_guard.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 | from fastapi import Request
4 |
5 | from fastapi.security import HTTPBearer
6 |
7 | from nest.core import Controller, Get, UseGuards, BaseGuard
8 |
9 |
10 | class SimpleGuard(BaseGuard):
11 | def __init__(self):
12 | self.called = False
13 |
14 | def can_activate(self, request: Request) -> bool:
15 | self.called = True
16 | return True
17 |
18 |
19 | class BearerGuard(BaseGuard):
20 | security_scheme = HTTPBearer()
21 |
22 | def can_activate(self, request: Request, credentials) -> bool:
23 | return True
24 |
25 |
26 | class JWTGuard(BaseGuard):
27 | security_scheme = HTTPBearer()
28 |
29 | def can_activate(self, request: Request, credentials=None) -> bool:
30 | if credentials and credentials.scheme == "Bearer":
31 | return self.validate_jwt(credentials.credentials)
32 | return False
33 |
34 |
35 | @Controller("/guard")
36 | class GuardController:
37 | @Get("/")
38 | @UseGuards(SimpleGuard)
39 | def root(self):
40 | return {"ok": True}
41 |
42 |
43 | def test_use_guards_sets_attribute():
44 | assert hasattr(GuardController.root, "__guards__")
45 | assert SimpleGuard in GuardController.root.__guards__
46 |
47 |
48 | def test_guard_added_to_route_dependencies():
49 | router = GuardController.get_router()
50 | route = router.routes[0]
51 | deps = route.dependencies
52 | assert len(deps) == 1
53 | assert callable(deps[0].dependency)
54 |
55 |
56 | def _has_security_requirements(dependant):
57 | """Recursively check if a dependant or its dependencies have security requirements."""
58 | if dependant.security_requirements:
59 | return True
60 |
61 | for dep in dependant.dependencies:
62 | if _has_security_requirements(dep):
63 | return True
64 |
65 | return False
66 |
67 |
68 | def test_openapi_security_requirement():
69 | @Controller("/bearer")
70 | class BearerController:
71 | @Get("/")
72 | @UseGuards(BearerGuard)
73 | def root(self):
74 | return {"ok": True}
75 |
76 | router = BearerController.get_router()
77 | route = router.routes[0]
78 |
79 | # Check if security requirements exist anywhere in the dependency tree
80 | assert _has_security_requirements(route.dependant), \
81 | "Security requirements should be present in the dependency tree for OpenAPI integration"
82 |
--------------------------------------------------------------------------------
/tests/test_core/test_pynest_application.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from nest.core import PyNestApp
4 | from tests.test_core import test_container, test_resolver
5 | from tests.test_core.test_pynest_factory import test_server
6 |
7 |
8 | @pytest.fixture
9 | def pynest_app(test_container, test_server):
10 | return PyNestApp(container=test_container, http_server=test_server)
11 |
12 |
13 | def test_is_listening_property(pynest_app):
14 | assert not pynest_app.is_listening
15 | pynest_app._is_listening = (
16 | True # Directly modify the protected attribute for testing
17 | )
18 | assert pynest_app.is_listening
19 |
20 |
21 | def test_get_server_returns_http_server(pynest_app, test_server):
22 | assert pynest_app.get_server() == test_server
23 |
24 |
25 | def test_register_routes_calls_register_routes_on_resolver(pynest_app, test_resolver):
26 | pynest_app.routes_resolver = test_resolver
27 | pynest_app.register_routes()
28 | assert pynest_app.get_server().routes
29 |
--------------------------------------------------------------------------------
/tests/test_core/test_pynest_container.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from nest.core import Module, PyNestContainer
4 | from tests.test_core import test_module
5 |
6 |
7 | @pytest.fixture(scope="module")
8 | def container():
9 | # Since PyNestContainer is a singleton, we clear its state before each test to ensure test isolation
10 | PyNestContainer()._instance = None
11 | return PyNestContainer()
12 |
13 |
14 | def test_singleton_pattern(container):
15 | second_container = PyNestContainer()
16 | assert (
17 | container is second_container
18 | ), "PyNestContainer should implement the singleton pattern"
19 |
20 |
21 | def test_add_module(container, test_module):
22 | result = container.add_module(test_module)
23 | assert result["inserted"] is True, "Module should be added successfully"
24 | module_ref = result["module_ref"]
25 | assert module_ref is not None, "Module reference should not be None"
26 | assert container.modules.has(
27 | module_ref.token
28 | ), "Module should be added to the container"
29 |
--------------------------------------------------------------------------------
/tests/test_core/test_pynest_factory.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from fastapi import FastAPI
3 |
4 | from nest.core import PyNestFactory # Replace 'your_module' with the actual module name
5 | from tests.test_core import test_container, test_module, test_server
6 |
7 |
8 | def test_create_server(test_server):
9 | assert isinstance(test_server, FastAPI)
10 | assert test_server.title == "Test Server"
11 | assert test_server.description == "This is a test server"
12 | assert test_server.version == "1.0.0"
13 | assert test_server.debug is True
14 |
15 |
16 | def test_e2e(test_module):
17 | app = PyNestFactory.create(
18 | test_module,
19 | title="Test Server",
20 | description="This is a test server",
21 | version="1.0.0",
22 | debug=True,
23 | )
24 | http_server = app.get_server()
25 | assert isinstance(http_server, FastAPI)
26 | assert http_server.title == "Test Server"
27 | assert http_server.description == "This is a test server"
28 | assert http_server.version == "1.0.0"
29 | assert http_server.debug is True
30 |
--------------------------------------------------------------------------------