├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .env.example ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .pylintrc ├── .theia └── launch.json ├── .vscode └── settings.json ├── CHANGE_LOG.md ├── Dockerfile.render ├── Pipfile ├── Pipfile.lock ├── Procfile ├── README.es.md ├── README.md ├── database.sh ├── docs ├── HELP.md └── assets │ ├── .assets │ ├── badge.png │ ├── db_config.gif │ ├── debugging.gif │ ├── debugging_icon.png │ ├── env_variables.gif │ ├── how-to.png │ ├── reset_migrations.bash │ ├── rigo-baby.jpeg │ └── welcome.py ├── migrations ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ └── a5cffa318ac2_.py ├── pycodestyle.cfg ├── render.yaml ├── render_build.sh └── src ├── admin.py ├── app.py ├── models.py ├── utils.py └── wsgi.py /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/python:3.13 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | # [Optional] If your requirements rarely change, uncomment this section to add them to the image. 6 | # COPY requirements.txt /tmp/pip-tmp/ 7 | # RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ 8 | # && rm -rf /tmp/pip-tmp 9 | 10 | # Installs system dependencies required for PostgreSQL and Graphviz, and cleans up to reduce image size 11 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 12 | && apt-get -y install --no-install-recommends \ 13 | postgresql-client \ 14 | graphviz \ 15 | graphviz-dev \ 16 | && rm -rf /var/lib/apt/lists/* 17 | 18 | 19 | # Install `pipenv` globally 20 | RUN pip install pipenv 21 | 22 | # Use the same path as `workspaceFolder` in `devcontainer.json` 23 | WORKDIR /workspaces/${localWorkspaceFolderBasename} 24 | 25 | # Copy Pipfile and Pipfile.lock first to leverage Docker cache 26 | COPY Pipfile Pipfile.lock ./ 27 | 28 | # Install Python dependencies with `pipenv` 29 | RUN pipenv install --dev --deploy --skip-lock 30 | 31 | # Copy the rest of the code after installing dependencies 32 | COPY . ./ 33 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/postgres 3 | { 4 | "name": "Python 3 & PostgreSQL", 5 | "dockerComposeFile": "docker-compose.yml", 6 | "service": "app", 7 | "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", 8 | "customizations": { 9 | "vscode": { 10 | "settings": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode", 12 | "workbench.editorAssociations": { 13 | "*.md": "vscode.markdown.preview.editor" 14 | } 15 | }, 16 | "extensions": [ "esbenp.prettier-vscode", "MS-vsliveshare.vsliveshare" ] 17 | } 18 | }, 19 | 20 | // Features to add to the dev container. More info: https://containers.dev/features. 21 | // "features": {}, 22 | 23 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 24 | // This can be used to network with other containers or the host. 25 | "forwardPorts": [5432, 3000], 26 | 27 | "portsAttributes": { 28 | "3000": { 29 | "visibility": "public" 30 | } 31 | }, 32 | 33 | "onCreateCommand": "cp -n .env.example .env && pipenv install", 34 | 35 | // Use 'postCreateCommand' to run commands after the container is created. 36 | "postCreateCommand": "pipenv install && bash database.sh && python docs/assets/welcome.py", 37 | 38 | // Configure tool-specific properties. 39 | // "customizations": {}, 40 | 41 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 42 | // "remoteUser": "root" 43 | } 44 | -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | build: 6 | context: .. 7 | dockerfile: .devcontainer/Dockerfile 8 | 9 | volumes: 10 | - ../..:/workspaces:cached 11 | 12 | # Overrides default command so things don't shut down after the process ends. 13 | command: sleep infinity 14 | 15 | # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. 16 | network_mode: service:db 17 | 18 | # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. 19 | # (Adding the "ports" property to this file will not forward from a Codespace.) 20 | 21 | db: 22 | image: postgres:latest 23 | restart: unless-stopped 24 | volumes: 25 | - postgres-data:/var/lib/postgresql/data 26 | environment: 27 | POSTGRES_USER: gitpod 28 | POSTGRES_DB: example 29 | POSTGRES_PASSWORD: postgres 30 | 31 | # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. 32 | # (Adding the "ports" property to this file will not forward from a Codespace.) 33 | 34 | volumes: 35 | postgres-data: 36 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://gitpod:postgres@localhost:5432/example 2 | FLASK_APP_KEY="any key works" 3 | FLASK_APP=src/app.py 4 | FLASK_DEBUG=1 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | database.database 4 | database.db 5 | diagram.png 6 | public/* 7 | dist/* 8 | package.json 9 | !.vscode 10 | __pycache__/ -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-postgres:latest 2 | 3 | SHELL ["/bin/bash", "-c"] 4 | 5 | RUN sudo apt-get update \ 6 | && sudo apt-get update \ 7 | && sudo apt-get clean \ 8 | && sudo rm -rf /var/cache/apt/* /var/lib/apt/lists/* /tmp/* 9 | 10 | # That Gitpod install pyenv for me? no, thanks 11 | WORKDIR /home/gitpod/ 12 | RUN rm .pyenv -Rf 13 | RUN rm .gp_pyenv.d -Rf 14 | RUN curl https://pyenv.run | bash 15 | 16 | 17 | RUN pyenv update && pyenv install 3.10.7 && pyenv global 3.10.7 18 | RUN pip install pipenv 19 | RUN npm i heroku -g 20 | 21 | # remove PIP_USER environment 22 | USER gitpod 23 | RUN if ! grep -q "export PIP_USER=no" "$HOME/.bashrc"; then printf '%s\n' "export PIP_USER=no" >> "$HOME/.bashrc"; fi 24 | RUN echo "" >> $HOME/.bashrc 25 | RUN echo "unset DATABASE_URL" >> $HOME/.bashrc 26 | RUN echo "export DATABASE_URL" >> $HOME/.bashrc -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | ports: 4 | - port: 3000 5 | onOpen: open-preview 6 | visibility: public 7 | - port: 3306 8 | onOpen: ignore 9 | visibility: public 10 | tasks: 11 | - init: > 12 | (cp -n .env.example .env || true) && 13 | pipenv install && 14 | psql -U gitpod -c 'CREATE DATABASE example;' && 15 | psql -U gitpod -c 'CREATE EXTENSION unaccent;' -d example && 16 | psql -c "ALTER USER gitpod PASSWORD 'postgres';" && 17 | bash database.sh && 18 | python docs/assets/welcome.py 19 | command: > 20 | pipenv run start; 21 | 22 | github: 23 | prebuilds: 24 | # enable for the master/default branch (defaults to true) 25 | main: false 26 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | init-hook='import sys; sys.path.append("/workspace/flask-rest-hello")' 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # Deprecated. It was used to include message's id in output. Use --msg-template 25 | # instead. 26 | include-ids=no 27 | 28 | # Deprecated. It was used to include symbolic ids of messages in output. Use 29 | # --msg-template instead. 30 | symbols=no 31 | 32 | # Use multiple processes to speed up Pylint. 33 | jobs=1 34 | 35 | # Allow loading of arbitrary C extensions. Extensions are imported into the 36 | # active Python interpreter and may run arbitrary code. 37 | unsafe-load-any-extension=no 38 | 39 | # A comma-separated list of package or module names from where C extensions may 40 | # be loaded. Extensions are loading into the active Python interpreter and may 41 | # run arbitrary code 42 | extension-pkg-whitelist= 43 | 44 | # Allow optimization of some AST trees. This will activate a peephole AST 45 | # optimizer, which will apply various small optimizations. For instance, it can 46 | # be used to obtain the result of joining multiple strings with the addition 47 | # operator. Joining a lot of strings can lead to a maximum recursion error in 48 | # Pylint and this flag can prevent that. It has one side effect, the resulting 49 | # AST will be different than the one from reality. 50 | optimize-ast=no 51 | 52 | 53 | [MESSAGES CONTROL] 54 | 55 | # Only show warnings with the listed confidence levels. Leave empty to show 56 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 57 | confidence= 58 | 59 | # Enable the message, report, category or checker with the given id(s). You can 60 | # either give multiple identifier separated by comma (,) or put this option 61 | # multiple time. See also the "--disable" option for examples. 62 | #enable= 63 | 64 | # Disable the message, report, category or checker with the given id(s). You 65 | # can either give multiple identifiers separated by comma (,) or put this 66 | # option multiple times (only on the command line, not in the configuration 67 | # file where it should appear only once).You can also use "--disable=all" to 68 | # disable everything first and then reenable specific checks. For example, if 69 | # you want to run only the similarities checker, you can use "--disable=all 70 | # --enable=similarities". If you want to run only the classes checker, but have 71 | # no Warning level messages displayed, use"--disable=all --enable=classes 72 | # --disable=W" 73 | disable=E1608,W1627,E1601,E1603,E1602,E1605,E1604,E1607,E1606,W1621,W1620,W1623,W1622,W1625,W1624,W1609,W1608,W1607,W1606,W1605,W1604,W1603,W1602,W1601,W1639,W1640,I0021,W1638,I0020,W1618,W1619,W1630,W1626,W1637,W1634,W1635,W1610,W1611,W1612,W1613,W1614,W1615,W1616,W1617,W1632,W1633,W0704,W1628,W1629,W1636 74 | 75 | 76 | [REPORTS] 77 | 78 | # Set the output format. Available formats are text, parseable, colorized, msvs 79 | # (visual studio) and html. You can also give a reporter class, eg 80 | # mypackage.mymodule.MyReporterClass. 81 | output-format=text 82 | 83 | # Put messages in a separate file for each module / package specified on the 84 | # command line instead of printing them on stdout. Reports (if any) will be 85 | # written in a file name "pylint_global.[txt|html]". 86 | files-output=no 87 | 88 | # Tells whether to display a full report or only the messages 89 | reports=yes 90 | 91 | # Python expression which should return a note less than 10 (10 is the highest 92 | # note). You have access to the variables errors warning, statement which 93 | # respectively contain the number of errors / warnings messages and the total 94 | # number of statements analyzed. This is used by the global evaluation report 95 | # (RP0004). 96 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 97 | 98 | # Add a comment according to your evaluation note. This is used by the global 99 | # evaluation report (RP0004). 100 | comment=no 101 | 102 | # Template used to display messages. This is a python new-style format string 103 | # used to format the message information. See doc for all details 104 | #msg-template= 105 | 106 | 107 | [BASIC] 108 | 109 | # Required attributes for module, separated by a comma 110 | required-attributes= 111 | 112 | # List of builtins function names that should not be used, separated by a comma 113 | bad-functions=map,filter,input 114 | 115 | # Good variable names which should always be accepted, separated by a comma 116 | good-names=i,j,k,ex,Run,_ 117 | 118 | # Bad variable names which should always be refused, separated by a comma 119 | bad-names=foo,bar,baz,toto,tutu,tata 120 | 121 | # Colon-delimited sets of names that determine each other's naming style when 122 | # the name regexes allow several styles. 123 | name-group= 124 | 125 | # Include a hint for the correct naming format with invalid-name 126 | include-naming-hint=no 127 | 128 | # Regular expression matching correct function names 129 | function-rgx=[a-z][A-Za-z0-9]{1,30}$ 130 | 131 | # Naming hint for function names 132 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 133 | 134 | # Regular expression matching correct variable names 135 | variable-rgx=[a-z][A-Za-z0-9]{1,30}$ 136 | 137 | # Naming hint for variable names 138 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 139 | 140 | # Regular expression matching correct constant names 141 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 142 | 143 | # Naming hint for constant names 144 | const-name-hint=(([a-zA-Z_][A-Z0-9_]*)|(__.*__))$ 145 | 146 | # Regular expression matching correct attribute names 147 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 148 | 149 | # Naming hint for attribute names 150 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 151 | 152 | # Regular expression matching correct argument names 153 | argument-rgx=[a-z][A-Za-z0-9]{1,30}$ 154 | 155 | # Naming hint for argument names 156 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 157 | 158 | # Regular expression matching correct class attribute names 159 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 160 | 161 | # Naming hint for class attribute names 162 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 163 | 164 | # Regular expression matching correct inline iteration names 165 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 166 | 167 | # Naming hint for inline iteration names 168 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 169 | 170 | # Regular expression matching correct class names 171 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 172 | 173 | # Naming hint for class names 174 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 175 | 176 | # Regular expression matching correct module names 177 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 178 | 179 | # Naming hint for module names 180 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 181 | 182 | # Regular expression matching correct method names 183 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 184 | 185 | # Naming hint for method names 186 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 187 | 188 | # Regular expression which should only match function or class names that do 189 | # not require a docstring. 190 | no-docstring-rgx=__.*__ 191 | 192 | # Minimum line length for functions/classes that require docstrings, shorter 193 | # ones are exempt. 194 | docstring-min-length=-1 195 | 196 | 197 | [FORMAT] 198 | 199 | # Maximum number of characters on a single line. 200 | max-line-length=120 201 | 202 | # Regexp for a line that is allowed to be longer than the limit. 203 | ignore-long-lines=^\s*(# )??$ 204 | 205 | # Allow the body of an if to be on the same line as the test if there is no 206 | # else. 207 | single-line-if-stmt=no 208 | 209 | # List of optional constructs for which whitespace checking is disabled 210 | no-space-check=trailing-comma,dict-separator 211 | 212 | # Maximum number of lines in a module 213 | max-module-lines=1000 214 | 215 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 216 | # tab). 217 | indent-string=' ' 218 | 219 | # Number of spaces of indent required inside a hanging or continued line. 220 | indent-after-paren=4 221 | 222 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 223 | expected-line-ending-format= 224 | 225 | 226 | [LOGGING] 227 | 228 | # Logging modules to check that the string format arguments are in logging 229 | # function parameter format 230 | logging-modules=logging 231 | 232 | 233 | [MISCELLANEOUS] 234 | 235 | # List of note tags to take in consideration, separated by a comma. 236 | notes=FIXME,XXX,TODO 237 | 238 | 239 | [SIMILARITIES] 240 | 241 | # Minimum lines number of a similarity. 242 | min-similarity-lines=4 243 | 244 | # Ignore comments when computing similarities. 245 | ignore-comments=yes 246 | 247 | # Ignore docstrings when computing similarities. 248 | ignore-docstrings=yes 249 | 250 | # Ignore imports when computing similarities. 251 | ignore-imports=no 252 | 253 | 254 | [SPELLING] 255 | 256 | # Spelling dictionary name. Available dictionaries: none. To make it working 257 | # install python-enchant package. 258 | spelling-dict= 259 | 260 | # List of comma separated words that should not be checked. 261 | spelling-ignore-words= 262 | 263 | # A path to a file that contains private dictionary; one word per line. 264 | spelling-private-dict-file= 265 | 266 | # Tells whether to store unknown words to indicated private dictionary in 267 | # --spelling-private-dict-file option instead of raising a message. 268 | spelling-store-unknown-words=no 269 | 270 | 271 | [TYPECHECK] 272 | 273 | # Tells whether missing members accessed in mixin class should be ignored. A 274 | # mixin class is detected if its name ends with "mixin" (case insensitive). 275 | ignore-mixin-members=yes 276 | 277 | # List of module names for which member attributes should not be checked 278 | # (useful for modules/projects where namespaces are manipulated during runtime 279 | # and thus existing member attributes cannot be deduced by static analysis 280 | ignored-modules= 281 | 282 | # List of classes names for which member attributes should not be checked 283 | # (useful for classes with attributes dynamically set). 284 | ignored-classes=SQLObject 285 | 286 | # When zope mode is activated, add a predefined set of Zope acquired attributes 287 | # to generated-members. 288 | zope=no 289 | 290 | # List of members which are set dynamically and missed by pylint inference 291 | # system, and so shouldn't trigger E0201 when accessed. Python regular 292 | # expressions are accepted. 293 | generated-members=REQUEST,acl_users,aq_parent 294 | 295 | 296 | [VARIABLES] 297 | 298 | # Tells whether we should check for unused import in __init__ files. 299 | init-import=no 300 | 301 | # A regular expression matching the name of dummy variables (i.e. expectedly 302 | # not used). 303 | dummy-variables-rgx=_$|dummy 304 | 305 | # List of additional names supposed to be defined in builtins. Remember that 306 | # you should avoid to define new builtins when possible. 307 | additional-builtins= 308 | 309 | # List of strings which can identify a callback function by name. A callback 310 | # name must start or end with one of those strings. 311 | callbacks=cb_,_cb 312 | 313 | 314 | [CLASSES] 315 | 316 | # List of interface methods to ignore, separated by a comma. This is used for 317 | # instance to not check methods defines in Zope's Interface base class. 318 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 319 | 320 | # List of method names used to declare (i.e. assign) instance attributes. 321 | defining-attr-methods=__init__,__new__,setUp 322 | 323 | # List of valid names for the first argument in a class method. 324 | valid-classmethod-first-arg=cls 325 | 326 | # List of valid names for the first argument in a metaclass class method. 327 | valid-metaclass-classmethod-first-arg=mcs 328 | 329 | # List of member names, which should be excluded from the protected access 330 | # warning. 331 | exclude-protected=_asdict,_fields,_replace,_source,_make 332 | 333 | 334 | [DESIGN] 335 | 336 | # Maximum number of arguments for function / method 337 | max-args=5 338 | 339 | # Argument names that match this expression will be ignored. Default to name 340 | # with leading underscore 341 | ignored-argument-names=_.* 342 | 343 | # Maximum number of locals for function / method body 344 | max-locals=15 345 | 346 | # Maximum number of return / yield for function / method body 347 | max-returns=6 348 | 349 | # Maximum number of branch for function / method body 350 | max-branches=12 351 | 352 | # Maximum number of statements in function / method body 353 | max-statements=50 354 | 355 | # Maximum number of parents for a class (see R0901). 356 | max-parents=7 357 | 358 | # Maximum number of attributes for a class (see R0902). 359 | max-attributes=7 360 | 361 | # Minimum number of public methods for a class (see R0903). 362 | min-public-methods=2 363 | 364 | # Maximum number of public methods for a class (see R0904). 365 | max-public-methods=20 366 | 367 | 368 | [IMPORTS] 369 | 370 | # Deprecated modules which should not be used, separated by a comma 371 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 372 | 373 | # Create a graph of every (i.e. internal and external) dependencies in the 374 | # given file (report RP0402 must not be disabled) 375 | import-graph= 376 | 377 | # Create a graph of external dependencies in the given file (report RP0402 must 378 | # not be disabled) 379 | ext-import-graph= 380 | 381 | # Create a graph of internal dependencies in the given file (report RP0402 must 382 | # not be disabled) 383 | int-import-graph= 384 | 385 | 386 | [EXCEPTIONS] 387 | 388 | # Exceptions that will emit a warning when being caught. Defaults to 389 | # "Exception" 390 | overgeneral-exceptions=Exception -------------------------------------------------------------------------------- /.theia/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | "version": "0.2.0", 5 | "configurations": [ 6 | { 7 | "name": "Flask Server", 8 | "type": "python", 9 | "request": "launch", 10 | "program": "${workspaceFolder}/src/app.py", 11 | "console": "internalConsole" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "workbench.editorAssociations": { 5 | "*.md": "vscode.markdown.preview.editor" 6 | } 7 | } -------------------------------------------------------------------------------- /CHANGE_LOG.md: -------------------------------------------------------------------------------- 1 | # CHANGE LOG 2 | 3 | Here we are tracking the previous and upcoming changes (roadmap), pull request this file or open an issue if you have any suggestions for the next version of the boilerplate. 4 | 5 | ### Jan 26, 2021 6 | - Changed the main branch from `master` to `main` and updated all the docs and files to avoid bugs. 7 | 8 | ### Sep 14, 2020 9 | - Added tutorial on [how to reset the migrations](https://github.com/4GeeksAcademy/flask-rest-hello/blob/master/docs/RESET_MIGRATIONS.md). 10 | 11 | ### Jul 1st, 2020 12 | - Added video explanation of the boilerplate 13 | 14 | ### Jun 29th, 2020 15 | - [x] Added [Flask Admin](https://flask-admin.readthedocs.io/en/latest/) to easy manage the database without any extra code code and automatic CRUD operations for all models. 16 | 17 | ### Sep 16th, 2019 18 | - [x] Added debuging functionality 19 | 20 | ### August 9th, 2019 21 | - [x] Removed eralchemy from the Pipfile because its compatibility with Apply and PC computers its not clear. There is now way to create a database diagra.png anymore. 22 | - [x] Added this changelog file to keep track of changes on the boilerplate. 23 | - [x] Added documentation for [data validations](https://github.com/4GeeksAcademy/flask-rest-hello/blob/master/docs/DATA_VALIDATIONS.md) 24 | - [x] Added documentation for [SQL Alchemy operations](https://github.com/4GeeksAcademy/flask-rest-hello/edit/master/docs/MYSQL.md). 25 | 26 | ## Roadmap v2.0 27 | 28 | - [ ] Update documentation with more examples 29 | -------------------------------------------------------------------------------- /Dockerfile.render: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | RUN apt update \ 4 | && apt install software-properties-common \ 5 | && add-apt-repository ppa:deadsnakes/ppa \ 6 | && apt update \ 7 | && apt install python3.10 8 | 9 | WORKDIR /opt/app 10 | COPY --from=build /opt/app/venv /venv 11 | 12 | ENV PATH="/opt/app/venv/bin:$PATH" 13 | ENV NODE_ENV=container -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | sqlalchemy = "*" 11 | flask-sqlalchemy = "*" 12 | flask-migrate = "==4.0.5" 13 | flask-swagger = "==0.2.14" 14 | psycopg2-binary = "*" 15 | python-dotenv = "==1.0.0" 16 | mysqlclient = "==2.2.0" 17 | flask-cors = "==4.0.0" 18 | gunicorn = "*" 19 | flask-admin = "==1.6.1" 20 | wtforms = "==3.0.1" 21 | eralchemy2 = "*" 22 | 23 | [requires] 24 | python_version = "3.13" 25 | 26 | [scripts] 27 | start="flask run -p 3000 -h 0.0.0.0" 28 | init="flask db init" 29 | migrate="flask db migrate" 30 | upgrade="flask db upgrade" 31 | diagram = "eralchemy2 -i 'postgresql://gitpod:postgres@localhost:5432/example' -o diagram.png" 32 | deploy="echo 'Please follow this 3 steps to deploy: https://start.4geeksacademy.com/deploy/render' " 33 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "f52d0f60048e4fda416f74a9c7ec54390b4469b0ea7391e58187db90c1dc8f1f" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.13" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "alembic": { 20 | "hashes": [ 21 | "sha256:197de710da4b3e91cf66a826a5b31b5d59a127ab41bd0fc42863e2902ce2bbbe", 22 | "sha256:e1a1c738577bca1f27e68728c910cd389b9a92152ff91d902da649c192e30c49" 23 | ], 24 | "markers": "python_version >= '3.9'", 25 | "version": "==1.15.1" 26 | }, 27 | "blinker": { 28 | "hashes": [ 29 | "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", 30 | "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" 31 | ], 32 | "markers": "python_version >= '3.9'", 33 | "version": "==1.9.0" 34 | }, 35 | "click": { 36 | "hashes": [ 37 | "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", 38 | "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" 39 | ], 40 | "markers": "python_version >= '3.7'", 41 | "version": "==8.1.8" 42 | }, 43 | "eralchemy2": { 44 | "hashes": [ 45 | "sha256:1c1ee49b26287a459c5c89aa3eba3d6a9d1b8e6bd245d9fbacc6b15560fc66a5", 46 | "sha256:be3ecd4e696172fe4b439a3010b08564c6f7c7323b9e9b4369e47da7a7b48428" 47 | ], 48 | "index": "pypi", 49 | "markers": "python_version >= '3.8'", 50 | "version": "==1.4.1" 51 | }, 52 | "flask": { 53 | "hashes": [ 54 | "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", 55 | "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" 56 | ], 57 | "index": "pypi", 58 | "markers": "python_version >= '3.9'", 59 | "version": "==3.1.0" 60 | }, 61 | "flask-admin": { 62 | "hashes": [ 63 | "sha256:24cae2af832b6a611a01d7dc35f42d266c1d6c75a426b869d8cb241b78233369", 64 | "sha256:fd8190f1ec3355913a22739c46ed3623f1d82b8112cde324c60a6fc9b21c9406" 65 | ], 66 | "index": "pypi", 67 | "markers": "python_version >= '3.6'", 68 | "version": "==1.6.1" 69 | }, 70 | "flask-cors": { 71 | "hashes": [ 72 | "sha256:bc3492bfd6368d27cfe79c7821df5a8a319e1a6d5eab277a3794be19bdc51783", 73 | "sha256:f268522fcb2f73e2ecdde1ef45e2fd5c71cc48fe03cffb4b441c6d1b40684eb0" 74 | ], 75 | "index": "pypi", 76 | "version": "==4.0.0" 77 | }, 78 | "flask-migrate": { 79 | "hashes": [ 80 | "sha256:613a2df703998e78716cace68cd83972960834424457f5b67f56e74fff950aef", 81 | "sha256:d3f437a8b5f3849d1bb1b60e1b818efc564c66e3fefe90b62e5db08db295e1b1" 82 | ], 83 | "index": "pypi", 84 | "markers": "python_version >= '3.6'", 85 | "version": "==4.0.5" 86 | }, 87 | "flask-sqlalchemy": { 88 | "hashes": [ 89 | "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", 90 | "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312" 91 | ], 92 | "index": "pypi", 93 | "markers": "python_version >= '3.8'", 94 | "version": "==3.1.1" 95 | }, 96 | "flask-swagger": { 97 | "hashes": [ 98 | "sha256:3caddb1311388eafc86f82f8e64ba386a5df6b84e5f16dfae19ca08173eba216", 99 | "sha256:b4085f5bc36df4c20b6548cd1413adc9cf35719b0f0695367cd542065145294d" 100 | ], 101 | "index": "pypi", 102 | "version": "==0.2.14" 103 | }, 104 | "greenlet": { 105 | "hashes": [ 106 | "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", 107 | "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7", 108 | "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", 109 | "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", 110 | "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159", 111 | "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563", 112 | "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83", 113 | "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", 114 | "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395", 115 | "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa", 116 | "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", 117 | "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", 118 | "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", 119 | "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22", 120 | "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9", 121 | "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0", 122 | "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba", 123 | "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3", 124 | "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1", 125 | "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", 126 | "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291", 127 | "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39", 128 | "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", 129 | "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", 130 | "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", 131 | "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef", 132 | "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c", 133 | "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511", 134 | "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c", 135 | "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", 136 | "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", 137 | "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8", 138 | "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d", 139 | "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", 140 | "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145", 141 | "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80", 142 | "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", 143 | "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e", 144 | "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", 145 | "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1", 146 | "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef", 147 | "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc", 148 | "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", 149 | "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120", 150 | "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437", 151 | "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd", 152 | "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981", 153 | "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", 154 | "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a", 155 | "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798", 156 | "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7", 157 | "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", 158 | "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", 159 | "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", 160 | "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af", 161 | "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", 162 | "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", 163 | "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42", 164 | "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e", 165 | "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81", 166 | "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e", 167 | "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617", 168 | "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc", 169 | "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de", 170 | "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111", 171 | "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383", 172 | "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70", 173 | "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6", 174 | "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", 175 | "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", 176 | "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803", 177 | "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", 178 | "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" 179 | ], 180 | "markers": "python_version >= '3.7'", 181 | "version": "==3.1.1" 182 | }, 183 | "gunicorn": { 184 | "hashes": [ 185 | "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", 186 | "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec" 187 | ], 188 | "index": "pypi", 189 | "markers": "python_version >= '3.7'", 190 | "version": "==23.0.0" 191 | }, 192 | "itsdangerous": { 193 | "hashes": [ 194 | "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", 195 | "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" 196 | ], 197 | "markers": "python_version >= '3.8'", 198 | "version": "==2.2.0" 199 | }, 200 | "jinja2": { 201 | "hashes": [ 202 | "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", 203 | "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" 204 | ], 205 | "markers": "python_version >= '3.7'", 206 | "version": "==3.1.6" 207 | }, 208 | "mako": { 209 | "hashes": [ 210 | "sha256:95920acccb578427a9aa38e37a186b1e43156c87260d7ba18ca63aa4c7cbd3a1", 211 | "sha256:b5d65ff3462870feec922dbccf38f6efb44e5714d7b593a656be86663d8600ac" 212 | ], 213 | "markers": "python_version >= '3.8'", 214 | "version": "==1.3.9" 215 | }, 216 | "markupsafe": { 217 | "hashes": [ 218 | "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", 219 | "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", 220 | "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", 221 | "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", 222 | "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", 223 | "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", 224 | "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", 225 | "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", 226 | "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", 227 | "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", 228 | "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", 229 | "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", 230 | "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", 231 | "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", 232 | "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", 233 | "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", 234 | "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", 235 | "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", 236 | "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", 237 | "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", 238 | "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", 239 | "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", 240 | "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", 241 | "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", 242 | "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", 243 | "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", 244 | "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", 245 | "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", 246 | "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", 247 | "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", 248 | "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", 249 | "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", 250 | "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", 251 | "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", 252 | "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", 253 | "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", 254 | "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", 255 | "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", 256 | "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", 257 | "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", 258 | "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", 259 | "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", 260 | "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", 261 | "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", 262 | "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", 263 | "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", 264 | "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", 265 | "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", 266 | "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", 267 | "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", 268 | "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", 269 | "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", 270 | "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", 271 | "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", 272 | "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", 273 | "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", 274 | "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", 275 | "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", 276 | "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", 277 | "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", 278 | "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" 279 | ], 280 | "markers": "python_version >= '3.9'", 281 | "version": "==3.0.2" 282 | }, 283 | "mysqlclient": { 284 | "hashes": [ 285 | "sha256:004fe1d30d2c2ff8072f8ea513bcec235fd9b896f70dad369461d0ad7e570e98", 286 | "sha256:04368445f9c487d8abb7a878e3d23e923e6072c04a6c320f9e0dc8a82efba14e", 287 | "sha256:530ece9995a36cadb6211b9787f0c9e05cdab6702549bdb4236af5e9b535ed6a", 288 | "sha256:5670679ff1be1cc3fef0fa81bf39f0cd70605ba121141050f02743eb878ac114", 289 | "sha256:68837b6bb23170acffb43ae411e47533a560b6360c06dac39aa55700972c93b2", 290 | "sha256:955dba905a7443ce4788c63fdb9f8d688316260cf60b20ff51ac3b1c77616ede", 291 | "sha256:9c6b142836c7dba4f723bf9c93cc46b6e5081d65b2af807f400dda9eb85a16d0" 292 | ], 293 | "index": "pypi", 294 | "markers": "python_version >= '3.8'", 295 | "version": "==2.2.0" 296 | }, 297 | "packaging": { 298 | "hashes": [ 299 | "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", 300 | "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" 301 | ], 302 | "markers": "python_version >= '3.8'", 303 | "version": "==24.2" 304 | }, 305 | "psycopg2-binary": { 306 | "hashes": [ 307 | "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", 308 | "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5", 309 | "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f", 310 | "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", 311 | "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", 312 | "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c", 313 | "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", 314 | "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", 315 | "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", 316 | "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", 317 | "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", 318 | "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007", 319 | "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", 320 | "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92", 321 | "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", 322 | "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5", 323 | "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5", 324 | "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8", 325 | "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1", 326 | "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", 327 | "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", 328 | "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1", 329 | "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53", 330 | "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", 331 | "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906", 332 | "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0", 333 | "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", 334 | "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", 335 | "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", 336 | "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44", 337 | "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648", 338 | "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", 339 | "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", 340 | "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa", 341 | "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697", 342 | "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d", 343 | "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b", 344 | "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", 345 | "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4", 346 | "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287", 347 | "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", 348 | "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", 349 | "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", 350 | "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30", 351 | "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3", 352 | "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", 353 | "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92", 354 | "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", 355 | "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", 356 | "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", 357 | "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", 358 | "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", 359 | "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", 360 | "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc", 361 | "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", 362 | "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", 363 | "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", 364 | "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b", 365 | "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481", 366 | "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5", 367 | "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", 368 | "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", 369 | "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", 370 | "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", 371 | "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", 372 | "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", 373 | "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", 374 | "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863" 375 | ], 376 | "index": "pypi", 377 | "markers": "python_version >= '3.8'", 378 | "version": "==2.9.10" 379 | }, 380 | "pygraphviz": { 381 | "hashes": [ 382 | "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea" 383 | ], 384 | "markers": "python_version >= '3.10'", 385 | "version": "==1.14" 386 | }, 387 | "python-dotenv": { 388 | "hashes": [ 389 | "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", 390 | "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" 391 | ], 392 | "index": "pypi", 393 | "markers": "python_version >= '3.8'", 394 | "version": "==1.0.0" 395 | }, 396 | "pyyaml": { 397 | "hashes": [ 398 | "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", 399 | "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", 400 | "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", 401 | "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", 402 | "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", 403 | "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", 404 | "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", 405 | "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", 406 | "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", 407 | "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", 408 | "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", 409 | "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", 410 | "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", 411 | "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", 412 | "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", 413 | "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", 414 | "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", 415 | "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", 416 | "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", 417 | "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", 418 | "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", 419 | "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", 420 | "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", 421 | "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", 422 | "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", 423 | "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", 424 | "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", 425 | "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", 426 | "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", 427 | "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", 428 | "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", 429 | "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", 430 | "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", 431 | "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", 432 | "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", 433 | "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", 434 | "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", 435 | "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", 436 | "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", 437 | "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", 438 | "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", 439 | "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", 440 | "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", 441 | "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", 442 | "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", 443 | "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", 444 | "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", 445 | "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", 446 | "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", 447 | "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", 448 | "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", 449 | "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", 450 | "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" 451 | ], 452 | "markers": "python_version >= '3.8'", 453 | "version": "==6.0.2" 454 | }, 455 | "sqlalchemy": { 456 | "hashes": [ 457 | "sha256:018ee97c558b499b58935c5a152aeabf6d36b3d55d91656abeb6d93d663c0c4c", 458 | "sha256:01da15490c9df352fbc29859d3c7ba9cd1377791faeeb47c100832004c99472c", 459 | "sha256:04545042969833cb92e13b0a3019549d284fd2423f318b6ba10e7aa687690a3c", 460 | "sha256:06205eb98cb3dd52133ca6818bf5542397f1dd1b69f7ea28aa84413897380b06", 461 | "sha256:08cf721bbd4391a0e765fe0fe8816e81d9f43cece54fdb5ac465c56efafecb3d", 462 | "sha256:0d7e3866eb52d914aea50c9be74184a0feb86f9af8aaaa4daefe52b69378db0b", 463 | "sha256:125a7763b263218a80759ad9ae2f3610aaf2c2fbbd78fff088d584edf81f3782", 464 | "sha256:23c5aa33c01bd898f879db158537d7e7568b503b15aad60ea0c8da8109adf3e7", 465 | "sha256:2600a50d590c22d99c424c394236899ba72f849a02b10e65b4c70149606408b5", 466 | "sha256:2d7332868ce891eda48896131991f7f2be572d65b41a4050957242f8e935d5d7", 467 | "sha256:2ed107331d188a286611cea9022de0afc437dd2d3c168e368169f27aa0f61338", 468 | "sha256:3395e7ed89c6d264d38bea3bfb22ffe868f906a7985d03546ec7dc30221ea980", 469 | "sha256:344cd1ec2b3c6bdd5dfde7ba7e3b879e0f8dd44181f16b895940be9b842fd2b6", 470 | "sha256:34d5c49f18778a3665d707e6286545a30339ad545950773d43977e504815fa70", 471 | "sha256:35e72518615aa5384ef4fae828e3af1b43102458b74a8c481f69af8abf7e802a", 472 | "sha256:3eb14ba1a9d07c88669b7faf8f589be67871d6409305e73e036321d89f1d904e", 473 | "sha256:412c6c126369ddae171c13987b38df5122cb92015cba6f9ee1193b867f3f1530", 474 | "sha256:4600c7a659d381146e1160235918826c50c80994e07c5b26946a3e7ec6c99249", 475 | "sha256:463ecfb907b256e94bfe7bcb31a6d8c7bc96eca7cbe39803e448a58bb9fcad02", 476 | "sha256:4a06e6c8e31c98ddc770734c63903e39f1947c9e3e5e4bef515c5491b7737dde", 477 | "sha256:4b2de1523d46e7016afc7e42db239bd41f2163316935de7c84d0e19af7e69538", 478 | "sha256:4dabd775fd66cf17f31f8625fc0e4cfc5765f7982f94dc09b9e5868182cb71c0", 479 | "sha256:4eff9c270afd23e2746e921e80182872058a7a592017b2713f33f96cc5f82e32", 480 | "sha256:52607d0ebea43cf214e2ee84a6a76bc774176f97c5a774ce33277514875a718e", 481 | "sha256:533e0f66c32093a987a30df3ad6ed21170db9d581d0b38e71396c49718fbb1ca", 482 | "sha256:5493a8120d6fc185f60e7254fc056a6742f1db68c0f849cfc9ab46163c21df47", 483 | "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22", 484 | "sha256:5dfbc543578058c340360f851ddcecd7a1e26b0d9b5b69259b526da9edfa8875", 485 | "sha256:66a40003bc244e4ad86b72abb9965d304726d05a939e8c09ce844d27af9e6d37", 486 | "sha256:67de057fbcb04a066171bd9ee6bcb58738d89378ee3cabff0bffbf343ae1c787", 487 | "sha256:6827f8c1b2f13f1420545bd6d5b3f9e0b85fe750388425be53d23c760dcf176b", 488 | "sha256:6b35e07f1d57b79b86a7de8ecdcefb78485dab9851b9638c2c793c50203b2ae8", 489 | "sha256:7399d45b62d755e9ebba94eb89437f80512c08edde8c63716552a3aade61eb42", 490 | "sha256:788b6ff6728072b313802be13e88113c33696a9a1f2f6d634a97c20f7ef5ccce", 491 | "sha256:78f1b79132a69fe8bd6b5d91ef433c8eb40688ba782b26f8c9f3d2d9ca23626f", 492 | "sha256:79f4f502125a41b1b3b34449e747a6abfd52a709d539ea7769101696bdca6716", 493 | "sha256:7a8517b6d4005facdbd7eb4e8cf54797dbca100a7df459fdaff4c5123265c1cd", 494 | "sha256:7bd5c5ee1448b6408734eaa29c0d820d061ae18cb17232ce37848376dcfa3e92", 495 | "sha256:7f5243357e6da9a90c56282f64b50d29cba2ee1f745381174caacc50d501b109", 496 | "sha256:805cb481474e111ee3687c9047c5f3286e62496f09c0e82e8853338aaaa348f8", 497 | "sha256:871f55e478b5a648c08dd24af44345406d0e636ffe021d64c9b57a4a11518304", 498 | "sha256:87a1ce1f5e5dc4b6f4e0aac34e7bb535cb23bd4f5d9c799ed1633b65c2bcad8c", 499 | "sha256:8a10ca7f8a1ea0fd5630f02feb055b0f5cdfcd07bb3715fc1b6f8cb72bf114e4", 500 | "sha256:995c2bacdddcb640c2ca558e6760383dcdd68830160af92b5c6e6928ffd259b4", 501 | "sha256:9f03143f8f851dd8de6b0c10784363712058f38209e926723c80654c1b40327a", 502 | "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f", 503 | "sha256:a28f9c238f1e143ff42ab3ba27990dfb964e5d413c0eb001b88794c5c4a528a9", 504 | "sha256:b2cf5b5ddb69142511d5559c427ff00ec8c0919a1e6c09486e9c32636ea2b9dd", 505 | "sha256:b761a6847f96fdc2d002e29e9e9ac2439c13b919adfd64e8ef49e75f6355c548", 506 | "sha256:bf555f3e25ac3a70c67807b2949bfe15f377a40df84b71ab2c58d8593a1e036e", 507 | "sha256:c08a972cbac2a14810463aec3a47ff218bb00c1a607e6689b531a7c589c50723", 508 | "sha256:c457a38351fb6234781d054260c60e531047e4d07beca1889b558ff73dc2014b", 509 | "sha256:c4c433f78c2908ae352848f56589c02b982d0e741b7905228fad628999799de4", 510 | "sha256:d9f119e7736967c0ea03aff91ac7d04555ee038caf89bb855d93bbd04ae85b41", 511 | "sha256:e6b0a1c7ed54a5361aaebb910c1fa864bae34273662bb4ff788a527eafd6e14d", 512 | "sha256:f2bcb085faffcacf9319b1b1445a7e1cfdc6fb46c03f2dce7bc2d9a4b3c1cdc5", 513 | "sha256:fe193d3ae297c423e0e567e240b4324d6b6c280a048e64c77a3ea6886cc2aa87" 514 | ], 515 | "index": "pypi", 516 | "markers": "python_version >= '3.7'", 517 | "version": "==2.0.39" 518 | }, 519 | "typing-extensions": { 520 | "hashes": [ 521 | "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", 522 | "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" 523 | ], 524 | "markers": "python_version >= '3.8'", 525 | "version": "==4.12.2" 526 | }, 527 | "werkzeug": { 528 | "hashes": [ 529 | "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", 530 | "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" 531 | ], 532 | "markers": "python_version >= '3.9'", 533 | "version": "==3.1.3" 534 | }, 535 | "wtforms": { 536 | "hashes": [ 537 | "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc", 538 | "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b" 539 | ], 540 | "index": "pypi", 541 | "markers": "python_version >= '3.7'", 542 | "version": "==3.0.1" 543 | } 544 | }, 545 | "develop": {} 546 | } 547 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: pipenv run upgrade 2 | web: gunicorn wsgi --chdir ./src/ 3 | -------------------------------------------------------------------------------- /README.es.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Plantilla de Flask para Desarrolladores Junior 4 | 5 | Crea API's con Flask en minutos, [📹 mira el tutorial en video](https://youtu.be/ORxQ-K3BzQA). 6 | 7 | - [Documentación extensa aquí](https://start.4geeksacademy.com). 8 | - Integrado con Pipenv para la gestión de paquetes. 9 | - Despliegue rápido a render.com o heroku con `$ pipenv run deploy`. 10 | - Uso de archivo `.env`. 11 | - Integración de SQLAlchemy para la abstracción de bases de datos. 12 | 13 | ## 1) Instalación 14 | 15 | Esta plantilla se instala en unos segundos si la abres gratis con Codespaces (recomendado) o Gitpod. 16 | Omite estos pasos de instalación y salta al paso 2 si decides usar cualquiera de esos servicios. 17 | 18 | > Importante: La plantilla está hecha para python 3.10 pero puedes cambiar la `python_version` en el Pipfile. 19 | 20 | Los siguientes pasos se ejecutan automáticamente dentro de gitpod, si estás haciendo una instalación local debes hacerlos manualmente: 21 | 22 | ```sh 23 | pipenv install; 24 | psql -U root -c 'CREATE DATABASE example;' 25 | pipenv run init; 26 | pipenv run migrate; 27 | pipenv run upgrade; 28 | ``` 29 | 30 | > Nota: Los usuarios de Codespaces pueden conectarse a psql escribiendo: `psql -h localhost -U gitpod example` 31 | 32 | ## 2) Cómo empezar a codificar 33 | 34 | Hay una API de ejemplo funcionando con una base de datos de ejemplo. Todo tu código de aplicación debe escribirse dentro de la carpeta `./src/`. 35 | 36 | - src/main.py (aquí es donde debes codificar tus endpoints) 37 | - src/models.py (tus tablas de base de datos y lógica de serialización) 38 | - src/utils.py (algunas clases y funciones reutilizables) 39 | - src/admin.py (agrega tus modelos al administrador y gestiona tus datos fácilmente) 40 | 41 | Para una explicación más detallada, busca el tutorial dentro de la carpeta `docs`. 42 | 43 | ## Recuerda migrar cada vez que cambies tus modelos 44 | 45 | Debes migrar y actualizar las migraciones por cada actualización que hagas a tus modelos: 46 | 47 | ```bash 48 | $ pipenv run migrate # (para hacer las migraciones) 49 | $ pipenv run upgrade # (para actualizar tu base de datos con las migraciones) 50 | ``` 51 | 52 | ## Generar un diagrama de la base de datos 53 | 54 | Si deseas visualizar la estructura de tu base de datos en forma de diagrama, puedes generarlo con el siguiente comando: 55 | 56 | ```bash 57 | $ pipenv run diagram 58 | ``` 59 | 60 | Este comando generará un archivo con el diagrama de la base de datos basado en los modelos definidos en `src/models.py`. 61 | 62 | ## Verifica tu API en vivo 63 | 64 | 1. Una vez que ejecutes el comando `pipenv run start` tu API comenzará a ejecutarse en vivo y podrás abrirla haciendo clic en la pestaña "ports" y luego haciendo clic en "open browser". 65 | 66 | > ✋ Si estás trabajando en una nube de codificación como [Codespaces](https://docs.github.com/en/codespaces/developing-in-codespaces/forwarding-ports-in-your-codespace#sharing-a-port) o [Gitpod](https://www.gitpod.io/docs/configure/workspaces/ports#configure-port-visibility) asegúrate de que tu puerto reenviado sea público. 67 | 68 | ## Publica/Despliega tu sitio web! 69 | 70 | Esta plantilla está 100% lista para desplegarse con Render.com y Heroku en cuestión de minutos. Por favor lee la [documentación oficial al respecto](https://start.4geeksacademy.com/deploy). 71 | 72 | ### Contribuidores 73 | 74 | Esta plantilla fue construida como parte del [Bootcamp de Codificación](https://4geeksacademy.com/us/coding-bootcamp) de 4Geeks Academy por [Alejandro Sanchez](https://twitter.com/alesanchezr) y muchos otros contribuidores. Descubre más sobre nuestro [Curso de Desarrollador Full Stack](https://4geeksacademy.com/us/coding-bootcamps/part-time-full-stack-developer), y [Bootcamp de Ciencia de Datos](https://4geeksacademy.com/us/coding-bootcamps/datascience-machine-learning). 75 | 76 | Puedes encontrar otras plantillas y recursos como este en la [página de github de la escuela](https://github.com/4geeksacademy/). -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Flask Boilerplate for Junior Developers 4 | 5 | Create flask API's in minutes, [📹 watch the video tutorial](https://youtu.be/ORxQ-K3BzQA). 6 | 7 | - [Extensive documentation here](https://start.4geeksacademy.com). 8 | - Integrated with Pipenv for package managing. 9 | - Fast deloyment to render.com or heroku with `$ pipenv run deploy`. 10 | - Use of `.env` file. 11 | - SQLAlchemy integration for database abstraction. 12 | 13 | ## 1) Installation 14 | 15 | This template installs itself in a few seconds if you open it for free with Codespaces (recommended) or Gitpod. 16 | Skip this installation steps and jump to step 2 if you decide to use any of those services. 17 | 18 | > Important: The boiplerplate is made for python 3.10 but you can change the `python_version` on the Pipfile. 19 | 20 | The following steps are automatically runned withing gitpod, if you are doing a local installation you have to do them manually: 21 | 22 | ```sh 23 | pipenv install; 24 | psql -U root -c 'CREATE DATABASE example;' 25 | pipenv run init; 26 | pipenv run migrate; 27 | pipenv run upgrade; 28 | ``` 29 | 30 | > Note: Codespaces users can connect to psql by typing: `psql -h localhost -U gitpod example` 31 | 32 | ## 2) How to Start coding 33 | 34 | There is an example API working with an example database. All your application code should be written inside the `./src/` folder. 35 | 36 | - src/main.py (it's where your endpoints should be coded) 37 | - src/models.py (your database tables and serialization logic) 38 | - src/utils.py (some reusable classes and functions) 39 | - src/admin.py (add your models to the admin and manage your data easily) 40 | 41 | For a more detailed explanation, look for the tutorial inside the `docs` folder. 42 | 43 | ## Remember to migrate every time you change your models 44 | 45 | You have to migrate and upgrade the migrations for every update you make to your models: 46 | 47 | ```bash 48 | $ pipenv run migrate # (to make the migrations) 49 | $ pipenv run upgrade # (to update your databse with the migrations) 50 | ``` 51 | 52 | ## Generate a database diagram 53 | 54 | If you want to visualize the structure of your database in the form of a diagram, you can generate it with the following command: 55 | 56 | ```bash 57 | $ pipenv run diagram 58 | ``` 59 | 60 | This command will generate a file with the database diagram based on the models defined in `src/models.py`. 61 | 62 | ## Check your API live 63 | 64 | 1. Once you run the `pipenv run start` command your API will start running live and you can open it by clicking in the "ports" tab and then clicking "open browser". 65 | 66 | > ✋ If you are working on a coding cloud like [Codespaces](https://docs.github.com/en/codespaces/developing-in-codespaces/forwarding-ports-in-your-codespace#sharing-a-port) or [Gitpod](https://www.gitpod.io/docs/configure/workspaces/ports#configure-port-visibility) make sure that your forwared port is public. 67 | 68 | ## Publish/Deploy your website! 69 | 70 | This boilerplate it's 100% read to deploy with Render.com and Herkou in a matter of minutes. Please read the [official documentation about it](https://start.4geeksacademy.com/deploy). 71 | 72 | ### Contributors 73 | 74 | This template was built as part of the 4Geeks Academy [Coding Bootcamp](https://4geeksacademy.com/us/coding-bootcamp) by [Alejandro Sanchez](https://twitter.com/alesanchezr) and many other contributors. Find out more about our [Full Stack Developer Course](https://4geeksacademy.com/us/coding-bootcamps/part-time-full-stack-developer), and [Data Science Bootcamp](https://4geeksacademy.com/us/coding-bootcamps/datascience-machine-learning). 75 | 76 | You can find other templates and resources like this at the [school github page](https://github.com/4geeksacademy/). 77 | -------------------------------------------------------------------------------- /database.sh: -------------------------------------------------------------------------------- 1 | creating_migration () 2 | { 3 | pipenv run init 4 | pipenv run migrate 5 | pipenv run upgrade 6 | } 7 | 8 | migrate_upgrade () 9 | { 10 | pipenv run migrate 11 | pipenv run upgrade 12 | } 13 | 14 | dir=$(pwd) 15 | 16 | if [ ! -d $dir/migrations ] 17 | then 18 | echo 'creating migration' 19 | creating_migration 20 | else 21 | echo 'migrations already created' 22 | echo 'updating migrations' 23 | migrate_upgrade 24 | fi -------------------------------------------------------------------------------- /docs/HELP.md: -------------------------------------------------------------------------------- 1 | You can find a comprehensive documentation about this boilerplate here: 2 | https://start.4geeksacademy.com/starters/flask -------------------------------------------------------------------------------- /docs/assets/.assets: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/assets/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/badge.png -------------------------------------------------------------------------------- /docs/assets/db_config.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/db_config.gif -------------------------------------------------------------------------------- /docs/assets/debugging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/debugging.gif -------------------------------------------------------------------------------- /docs/assets/debugging_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/debugging_icon.png -------------------------------------------------------------------------------- /docs/assets/env_variables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/env_variables.gif -------------------------------------------------------------------------------- /docs/assets/how-to.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/how-to.png -------------------------------------------------------------------------------- /docs/assets/reset_migrations.bash: -------------------------------------------------------------------------------- 1 | rm -R -f ./migrations && 2 | pipenv run init && 3 | dropdb -h localhost -U gitpod example || true && 4 | createdb -h localhost -U gitpod example || true && 5 | psql -h localhost example -U gitpod -c 'CREATE EXTENSION unaccent;' || true && 6 | pipenv run migrate && 7 | pipenv run upgrade 8 | -------------------------------------------------------------------------------- /docs/assets/rigo-baby.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4GeeksAcademy/flask-rest-hello/2f0398872d1e7e7da2b8eea59e51007096708195/docs/assets/rigo-baby.jpeg -------------------------------------------------------------------------------- /docs/assets/welcome.py: -------------------------------------------------------------------------------- 1 | print(""" 2 | 3 | 4 | 5 | WELCOME GEEK! 🐍 + 💻 = 🤓 6 | 7 | The server is already running, \033[94mctr + c\033[0m to stop the server if you like 8 | 9 | The following commands are available to run your code: 10 | 11 | - \033[94m$ pipenv run migrate\033[0m create database migrations (if models.py is edited) 12 | - \033[94m$ pipenv run upgrade\033[0m run database migrations (if pending) 13 | - \033[94m$ pipenv run start\033[0m start flask web server (if not running) 14 | - \033[94m$ pipenv run deploy\033[0m deploy to heroku (if needed) \n\n 15 | """) -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Single-database configuration for Flask. 2 | -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,flask_migrate 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_flask_migrate] 38 | level = INFO 39 | handlers = 40 | qualname = flask_migrate 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from flask import current_app 7 | 8 | from alembic import context 9 | 10 | # this is the Alembic Config object, which provides 11 | # access to the values within the .ini file in use. 12 | config = context.config 13 | 14 | # Interpret the config file for Python logging. 15 | # This line sets up loggers basically. 16 | fileConfig(config.config_file_name) 17 | logger = logging.getLogger('alembic.env') 18 | 19 | # add your model's MetaData object here 20 | # for 'autogenerate' support 21 | # from myapp import mymodel 22 | # target_metadata = mymodel.Base.metadata 23 | config.set_main_option( 24 | 'sqlalchemy.url', 25 | str(current_app.extensions['migrate'].db.get_engine().url).replace( 26 | '%', '%%')) 27 | target_db = current_app.extensions['migrate'].db 28 | 29 | # other values from the config, defined by the needs of env.py, 30 | # can be acquired: 31 | # my_important_option = config.get_main_option("my_important_option") 32 | # ... etc. 33 | 34 | 35 | def get_metadata(): 36 | if hasattr(target_db, 'metadatas'): 37 | return target_db.metadatas[None] 38 | return target_db.metadata 39 | 40 | 41 | def run_migrations_offline(): 42 | """Run migrations in 'offline' mode. 43 | 44 | This configures the context with just a URL 45 | and not an Engine, though an Engine is acceptable 46 | here as well. By skipping the Engine creation 47 | we don't even need a DBAPI to be available. 48 | 49 | Calls to context.execute() here emit the given string to the 50 | script output. 51 | 52 | """ 53 | url = config.get_main_option("sqlalchemy.url") 54 | context.configure( 55 | url=url, target_metadata=get_metadata(), literal_binds=True 56 | ) 57 | 58 | with context.begin_transaction(): 59 | context.run_migrations() 60 | 61 | 62 | def run_migrations_online(): 63 | """Run migrations in 'online' mode. 64 | 65 | In this scenario we need to create an Engine 66 | and associate a connection with the context. 67 | 68 | """ 69 | 70 | # this callback is used to prevent an auto-migration from being generated 71 | # when there are no changes to the schema 72 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 73 | def process_revision_directives(context, revision, directives): 74 | if getattr(config.cmd_opts, 'autogenerate', False): 75 | script = directives[0] 76 | if script.upgrade_ops.is_empty(): 77 | directives[:] = [] 78 | logger.info('No changes in schema detected.') 79 | 80 | connectable = current_app.extensions['migrate'].db.get_engine() 81 | 82 | with connectable.connect() as connection: 83 | context.configure( 84 | connection=connection, 85 | target_metadata=get_metadata(), 86 | process_revision_directives=process_revision_directives, 87 | **current_app.extensions['migrate'].configure_args 88 | ) 89 | 90 | with context.begin_transaction(): 91 | context.run_migrations() 92 | 93 | 94 | if context.is_offline_mode(): 95 | run_migrations_offline() 96 | else: 97 | run_migrations_online() 98 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /migrations/versions/a5cffa318ac2_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: a5cffa318ac2 4 | Revises: 5 | Create Date: 2023-10-31 13:53:01.946815 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'a5cffa318ac2' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('user', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('email', sa.String(length=120), nullable=False), 24 | sa.Column('password', sa.String(length=80), nullable=False), 25 | sa.Column('is_active', sa.Boolean(), nullable=False), 26 | sa.PrimaryKeyConstraint('id'), 27 | sa.UniqueConstraint('email') 28 | ) 29 | # ### end Alembic commands ### 30 | 31 | 32 | def downgrade(): 33 | # ### commands auto generated by Alembic - please adjust! ### 34 | op.drop_table('user') 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /pycodestyle.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | ignore = E501, E302 -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | # This file was generated by Render's heroku-import Heroku CLI plugin 2 | # https://www.npmjs.com/package/@renderinc/heroku-import 3 | # Schema documented at https://render.com/docs/yaml-spec 4 | services: 5 | - type: web # valid values: https://render.com/docs/yaml-spec#type 6 | region: ohio 7 | name: flask-rest-hello 8 | env: python # valid values: https://render.com/docs/yaml-spec#environment 9 | buildCommand: "./render_build.sh" 10 | startCommand: "gunicorn wsgi --chdir ./src/" 11 | plan: free # optional; defaults to starter 12 | numInstances: 1 13 | envVars: 14 | - key: BASENAME 15 | value: / 16 | - key: FLASK_APP 17 | value: src/app.py 18 | - key: DEBUG 19 | value: TRUE 20 | - key: PYTHON_VERSION 21 | value: 3.10.6 22 | - key: DATABASE_URL # Render PostgreSQL database 23 | fromDatabase: 24 | name: flask-rest-42170 25 | property: connectionString 26 | 27 | databases: # Render PostgreSQL database 28 | - name: flask-rest-42170 29 | region: ohio 30 | ipAllowList: [] # only allow internal connections 31 | plan: free # optional; defaults to starter 32 | -------------------------------------------------------------------------------- /render_build.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bash 3 | # exit on error 4 | set -o errexit 5 | 6 | # npm install 7 | # npm run build 8 | 9 | pipenv install 10 | 11 | pipenv run upgrade -------------------------------------------------------------------------------- /src/admin.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask_admin import Admin 3 | from models import db, User 4 | from flask_admin.contrib.sqla import ModelView 5 | 6 | def setup_admin(app): 7 | app.secret_key = os.environ.get('FLASK_APP_KEY', 'sample key') 8 | app.config['FLASK_ADMIN_SWATCH'] = 'cerulean' 9 | admin = Admin(app, name='4Geeks Admin', template_mode='bootstrap3') 10 | 11 | 12 | # Add your models here, for example this is how we add a the User model to the admin 13 | admin.add_view(ModelView(User, db.session)) 14 | 15 | # You can duplicate that line to add mew models 16 | # admin.add_view(ModelView(YourModelName, db.session)) -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module takes care of starting the API Server, Loading the DB and Adding the endpoints 3 | """ 4 | import os 5 | from flask import Flask, request, jsonify, url_for 6 | from flask_migrate import Migrate 7 | from flask_swagger import swagger 8 | from flask_cors import CORS 9 | from utils import APIException, generate_sitemap 10 | from admin import setup_admin 11 | from models import db, User 12 | #from models import Person 13 | 14 | app = Flask(__name__) 15 | app.url_map.strict_slashes = False 16 | 17 | db_url = os.getenv("DATABASE_URL") 18 | if db_url is not None: 19 | app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace("postgres://", "postgresql://") 20 | else: 21 | app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:////tmp/test.db" 22 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 23 | 24 | MIGRATE = Migrate(app, db) 25 | db.init_app(app) 26 | CORS(app) 27 | setup_admin(app) 28 | 29 | # Handle/serialize errors like a JSON object 30 | @app.errorhandler(APIException) 31 | def handle_invalid_usage(error): 32 | return jsonify(error.to_dict()), error.status_code 33 | 34 | # generate sitemap with all your endpoints 35 | @app.route('/') 36 | def sitemap(): 37 | return generate_sitemap(app) 38 | 39 | @app.route('/user', methods=['GET']) 40 | def handle_hello(): 41 | 42 | response_body = { 43 | "msg": "Hello, this is your GET /user response " 44 | } 45 | 46 | return jsonify(response_body), 200 47 | 48 | # this only runs if `$ python src/app.py` is executed 49 | if __name__ == '__main__': 50 | PORT = int(os.environ.get('PORT', 3000)) 51 | app.run(host='0.0.0.0', port=PORT, debug=False) 52 | -------------------------------------------------------------------------------- /src/models.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | from sqlalchemy import String, Boolean 3 | from sqlalchemy.orm import Mapped, mapped_column 4 | 5 | db = SQLAlchemy() 6 | 7 | class User(db.Model): 8 | id: Mapped[int] = mapped_column(primary_key=True) 9 | email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) 10 | password: Mapped[str] = mapped_column(nullable=False) 11 | is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) 12 | 13 | 14 | def serialize(self): 15 | return { 16 | "id": self.id, 17 | "email": self.email, 18 | # do not serialize the password, its a security breach 19 | } 20 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, url_for 2 | 3 | class APIException(Exception): 4 | status_code = 400 5 | 6 | def __init__(self, message, status_code=None, payload=None): 7 | Exception.__init__(self) 8 | self.message = message 9 | if status_code is not None: 10 | self.status_code = status_code 11 | self.payload = payload 12 | 13 | def to_dict(self): 14 | rv = dict(self.payload or ()) 15 | rv['message'] = self.message 16 | return rv 17 | 18 | def has_no_empty_params(rule): 19 | defaults = rule.defaults if rule.defaults is not None else () 20 | arguments = rule.arguments if rule.arguments is not None else () 21 | return len(defaults) >= len(arguments) 22 | 23 | def generate_sitemap(app): 24 | links = ['/admin/'] 25 | for rule in app.url_map.iter_rules(): 26 | # Filter out rules we can't navigate to in a browser 27 | # and rules that require parameters 28 | if "GET" in rule.methods and has_no_empty_params(rule): 29 | url = url_for(rule.endpoint, **(rule.defaults or {})) 30 | if "/admin/" not in url: 31 | links.append(url) 32 | 33 | links_html = "".join(["
  • " + y + "
  • " for y in links]) 34 | return """ 35 |
    36 | 37 |

    Rigo welcomes you to your API!!

    38 |

    API HOST:

    39 |

    Start working on your proyect by following the Quick Start

    40 |

    Remember to specify a real endpoint path like:

    41 |
    " 42 | -------------------------------------------------------------------------------- /src/wsgi.py: -------------------------------------------------------------------------------- 1 | # This file was created to run the application on heroku using gunicorn. 2 | # Read more about it here: https://devcenter.heroku.com/articles/python-gunicorn 3 | 4 | from app import app as application 5 | 6 | if __name__ == "__main__": 7 | application.run() 8 | --------------------------------------------------------------------------------