├── .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(["
API HOST:
39 |Start working on your proyect by following the Quick Start
40 |Remember to specify a real endpoint path like:
41 |