mirror of https://github.com/nocturn9x/giambio.git
Added some exclude paths to gitignore
This commit is contained in:
parent
e29eaf3862
commit
70646a4767
|
@ -1,131 +1,134 @@
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
pip-wheel-metadata/
|
pip-wheel-metadata/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
htmlcov/
|
||||||
.tox/
|
.tox/
|
||||||
.nox/
|
.nox/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
.scrapy
|
.scrapy
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
pyenv.cfg
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# pipenv
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
# install all needed dependencies.
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
#Pipfile.lock
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
||||||
__pypackages__/
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
# Celery stuff
|
||||||
celerybeat.pid
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
# Environments
|
|
||||||
.env
|
# Environments
|
||||||
.venv
|
.env
|
||||||
env/
|
bin
|
||||||
venv/
|
lib
|
||||||
ENV/
|
.venv
|
||||||
env.bak/
|
env/
|
||||||
venv.bak/
|
venv/
|
||||||
|
ENV/
|
||||||
# Spyder project settings
|
env.bak/
|
||||||
.spyderproject
|
venv.bak/
|
||||||
.spyproject
|
|
||||||
|
# Spyder project settings
|
||||||
# Rope project settings
|
.spyderproject
|
||||||
.ropeproject
|
.spyproject
|
||||||
|
|
||||||
# mkdocs documentation
|
# Rope project settings
|
||||||
/site
|
.ropeproject
|
||||||
|
|
||||||
# mypy
|
# mkdocs documentation
|
||||||
.mypy_cache/
|
/site
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
# Pyre type checker
|
.dmypy.json
|
||||||
.pyre/
|
dmypy.json
|
||||||
|
|
||||||
.*.swp
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
.*.swp
|
||||||
|
|
374
LICENSE
374
LICENSE
|
@ -1,187 +1,187 @@
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
1. Definitions.
|
1. Definitions.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
the copyright owner that is granting the License.
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
other entities that control, are controlled by, or are under common
|
other entities that control, are controlled by, or are under common
|
||||||
control with that entity. For the purposes of this definition,
|
control with that entity. For the purposes of this definition,
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
direction or management of such entity, whether by contract or
|
direction or management of such entity, whether by contract or
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
exercising permissions granted by this License.
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
including but not limited to software source code, documentation
|
including but not limited to software source code, documentation
|
||||||
source, and configuration files.
|
source, and configuration files.
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
"Object" form shall mean any form resulting from mechanical
|
||||||
transformation or translation of a Source form, including but
|
transformation or translation of a Source form, including but
|
||||||
not limited to compiled object code, generated documentation,
|
not limited to compiled object code, generated documentation,
|
||||||
and conversions to other media types.
|
and conversions to other media types.
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
Object form, made available under the License, as indicated by a
|
Object form, made available under the License, as indicated by a
|
||||||
copyright notice that is included in or attached to the work
|
copyright notice that is included in or attached to the work
|
||||||
(an example is provided in the Appendix below).
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
form, that is based on (or derived from) the Work and for which the
|
form, that is based on (or derived from) the Work and for which the
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
of this License, Derivative Works shall not include works that remain
|
of this License, Derivative Works shall not include works that remain
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
the Work and Derivative Works thereof.
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
"Contribution" shall mean any work of authorship, including
|
||||||
the original version of the Work and any modifications or additions
|
the original version of the Work and any modifications or additions
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
means any form of electronic, verbal, or written communication sent
|
means any form of electronic, verbal, or written communication sent
|
||||||
to the Licensor or its representatives, including but not limited to
|
to the Licensor or its representatives, including but not limited to
|
||||||
communication on electronic mailing lists, source code control systems,
|
communication on electronic mailing lists, source code control systems,
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
excluding communication that is conspicuously marked or otherwise
|
excluding communication that is conspicuously marked or otherwise
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
subsequently incorporated within the Work.
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
Work and such Derivative Works in Source or Object form.
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
(except as stated in this section) patent license to make, have made,
|
(except as stated in this section) patent license to make, have made,
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
where such license applies only to those patent claims licensable
|
where such license applies only to those patent claims licensable
|
||||||
by such Contributor that are necessarily infringed by their
|
by such Contributor that are necessarily infringed by their
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
institute patent litigation against any entity (including a
|
institute patent litigation against any entity (including a
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
or contributory patent infringement, then any patent licenses
|
or contributory patent infringement, then any patent licenses
|
||||||
granted to You under this License for that Work shall terminate
|
granted to You under this License for that Work shall terminate
|
||||||
as of the date such litigation is filed.
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
modifications, and in Source or Object form, provided that You
|
modifications, and in Source or Object form, provided that You
|
||||||
meet the following conditions:
|
meet the following conditions:
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
(a) You must give any other recipients of the Work or
|
||||||
Derivative Works a copy of this License; and
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
(b) You must cause any modified files to carry prominent notices
|
||||||
stating that You changed the files; and
|
stating that You changed the files; and
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
that You distribute, all copyright, patent, trademark, and
|
that You distribute, all copyright, patent, trademark, and
|
||||||
attribution notices from the Source form of the Work,
|
attribution notices from the Source form of the Work,
|
||||||
excluding those notices that do not pertain to any part of
|
excluding those notices that do not pertain to any part of
|
||||||
the Derivative Works; and
|
the Derivative Works; and
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
distribution, then any Derivative Works that You distribute must
|
distribution, then any Derivative Works that You distribute must
|
||||||
include a readable copy of the attribution notices contained
|
include a readable copy of the attribution notices contained
|
||||||
within such NOTICE file, excluding those notices that do not
|
within such NOTICE file, excluding those notices that do not
|
||||||
pertain to any part of the Derivative Works, in at least one
|
pertain to any part of the Derivative Works, in at least one
|
||||||
of the following places: within a NOTICE text file distributed
|
of the following places: within a NOTICE text file distributed
|
||||||
as part of the Derivative Works; within the Source form or
|
as part of the Derivative Works; within the Source form or
|
||||||
documentation, if provided along with the Derivative Works; or,
|
documentation, if provided along with the Derivative Works; or,
|
||||||
within a display generated by the Derivative Works, if and
|
within a display generated by the Derivative Works, if and
|
||||||
wherever such third-party notices normally appear. The contents
|
wherever such third-party notices normally appear. The contents
|
||||||
of the NOTICE file are for informational purposes only and
|
of the NOTICE file are for informational purposes only and
|
||||||
do not modify the License. You may add Your own attribution
|
do not modify the License. You may add Your own attribution
|
||||||
notices within Derivative Works that You distribute, alongside
|
notices within Derivative Works that You distribute, alongside
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
that such additional attribution notices cannot be construed
|
that such additional attribution notices cannot be construed
|
||||||
as modifying the License.
|
as modifying the License.
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
You may add Your own copyright statement to Your modifications and
|
||||||
may provide additional or different license terms and conditions
|
may provide additional or different license terms and conditions
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
the conditions stated in this License.
|
the conditions stated in this License.
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
this License, without any additional terms or conditions.
|
this License, without any additional terms or conditions.
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
the terms of any separate license agreement you may have executed
|
the terms of any separate license agreement you may have executed
|
||||||
with Licensor regarding such Contributions.
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
except as required for reasonable and customary use in describing the
|
except as required for reasonable and customary use in describing the
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
implied, including, without limitation, any warranties or conditions
|
implied, including, without limitation, any warranties or conditions
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
appropriateness of using or redistributing the Work and assume any
|
appropriateness of using or redistributing the Work and assume any
|
||||||
risks associated with Your exercise of permissions under this License.
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
unless required by applicable law (such as deliberate and grossly
|
unless required by applicable law (such as deliberate and grossly
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
liable to You for damages, including any direct, indirect, special,
|
liable to You for damages, including any direct, indirect, special,
|
||||||
incidental, or consequential damages of any character arising as a
|
incidental, or consequential damages of any character arising as a
|
||||||
result of this License or out of the use or inability to use the
|
result of this License or out of the use or inability to use the
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
other commercial damages or losses), even if such Contributor
|
other commercial damages or losses), even if such Contributor
|
||||||
has been advised of the possibility of such damages.
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
or other liability obligations and/or rights consistent with this
|
or other liability obligations and/or rights consistent with this
|
||||||
License. However, in accepting such obligations, You may act only
|
License. However, in accepting such obligations, You may act only
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
defend, and hold each Contributor harmless for any liability
|
defend, and hold each Contributor harmless for any liability
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
of your accepting any such warranty or additional liability.
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
To apply the Apache License to your work, attach the following
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
replaced with your own identifying information. (Don't include
|
replaced with your own identifying information. (Don't include
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
comment syntax for the file format. We also recommend that a
|
comment syntax for the file format. We also recommend that a
|
||||||
file or class name and description of purpose be included on the
|
file or class name and description of purpose be included on the
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
760
README.md
760
README.md
|
@ -1,380 +1,380 @@
|
||||||
# giambio - Asynchronous Python made easy (and friendly)
|
# giambio - Asynchronous Python made easy (and friendly)
|
||||||
|
|
||||||
giambio is an event-driven concurrency library meant* to perform efficient and high-performant I/O multiplexing.
|
giambio is an event-driven concurrency library meant* to perform efficient and high-performant I/O multiplexing.
|
||||||
This library implements what is known as a _stackless mode of execution_, or
|
This library implements what is known as a _stackless mode of execution_, or
|
||||||
"green threads", though the latter term is misleading as **no multithreading is involved** (at least not by default).
|
"green threads", though the latter term is misleading as **no multithreading is involved** (at least not by default).
|
||||||
|
|
||||||
|
|
||||||
_*_: The library *works* (sometimes), but its still in its very early stages and is nowhere close being
|
_*_: The library *works* (sometimes), but its still in its very early stages and is nowhere close being
|
||||||
production ready, so be aware that it is likely that you'll find bugs and race conditions
|
production ready, so be aware that it is likely that you'll find bugs and race conditions
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
Right now this is nothing more than a toy implementation to help me understand how this whole `async`/`await` thing works
|
Right now this is nothing more than a toy implementation to help me understand how this whole `async`/`await` thing works
|
||||||
and it is pretty much guaranteed to explode spectacularly badly while using it. If you find any bugs, please report them!
|
and it is pretty much guaranteed to explode spectacularly badly while using it. If you find any bugs, please report them!
|
||||||
|
|
||||||
Oh and by the way, this project was hugely inspired by the [curio](https://github.com/dabeaz/curio) and the
|
Oh and by the way, this project was hugely inspired by the [curio](https://github.com/dabeaz/curio) and the
|
||||||
[trio](https://github.com/python-trio/trio) projects, you might want to have a look at their amazing work if you need a
|
[trio](https://github.com/python-trio/trio) projects, you might want to have a look at their amazing work if you need a
|
||||||
rock-solid and structured concurrency framework (I personally recommend trio and that's definitely not related to the fact
|
rock-solid and structured concurrency framework (I personally recommend trio and that's definitely not related to the fact
|
||||||
that most of the following text is ~~stolen~~ inspired from its documentation)
|
that most of the following text is ~~stolen~~ inspired from its documentation)
|
||||||
|
|
||||||
|
|
||||||
# What the hell is async anyway?
|
# What the hell is async anyway?
|
||||||
|
|
||||||
Libraries like giambio shine the most when it comes to performing asyncronous I/O (reading a socket, writing to a file, that sort of thing).
|
Libraries like giambio shine the most when it comes to performing asyncronous I/O (reading a socket, writing to a file, that sort of thing).
|
||||||
The most common example of this is a network server that needs to handle multiple connections at the same time.
|
The most common example of this is a network server that needs to handle multiple connections at the same time.
|
||||||
One possible approach to achieve concurrency is to use threads, and despite their bad reputation in Python, they
|
One possible approach to achieve concurrency is to use threads, and despite their bad reputation in Python, they
|
||||||
actually might be a good choice when it comes to I/O for reasons that span far beyond the scope of this tutorial.
|
actually might be a good choice when it comes to I/O for reasons that span far beyond the scope of this tutorial.
|
||||||
If you choose to use threads, there are a couple things you can do, involving what is known as _thread synchronization
|
If you choose to use threads, there are a couple things you can do, involving what is known as _thread synchronization
|
||||||
primitives_ and _thread pools_, but once again that is beyond the purposes of this quickstart guide.
|
primitives_ and _thread pools_, but once again that is beyond the purposes of this quickstart guide.
|
||||||
A library like giambio comes into play when you need to perform lots of [blocking operations](https://en.wikipedia.org/wiki/Blocking_(computing)),
|
A library like giambio comes into play when you need to perform lots of [blocking operations](https://en.wikipedia.org/wiki/Blocking_(computing)),
|
||||||
and network servers happen to be heavily based on I/O: a blocking operation.
|
and network servers happen to be heavily based on I/O: a blocking operation.
|
||||||
Starting to see where we're heading?
|
Starting to see where we're heading?
|
||||||
|
|
||||||
|
|
||||||
## A deeper dive
|
## A deeper dive
|
||||||
|
|
||||||
Giambio has been designed with simplicity in mind, so this document won't explain all the gritty details about _how_ async is
|
Giambio has been designed with simplicity in mind, so this document won't explain all the gritty details about _how_ async is
|
||||||
implemented in Python (you might want to check out [this article](https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/) if you want to learn more about all the implementation details).
|
implemented in Python (you might want to check out [this article](https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/) if you want to learn more about all the implementation details).
|
||||||
For the sake of this tutorial, all you need to know is that giambio is all about a feature added in Python 3.5:
|
For the sake of this tutorial, all you need to know is that giambio is all about a feature added in Python 3.5:
|
||||||
asynchronous functions, or 'async' for short.
|
asynchronous functions, or 'async' for short.
|
||||||
|
|
||||||
Async functions are functions defined with `async def` instead of the regular `def`, like so:
|
Async functions are functions defined with `async def` instead of the regular `def`, like so:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
async def async_fun(): # An async function
|
async def async_fun(): # An async function
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
|
|
||||||
def sync_fun(): # A regular (sync) function
|
def sync_fun(): # A regular (sync) function
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
First of all, async functions like to stick together: to call an async function you need to put `await` in front of it, like below:
|
First of all, async functions like to stick together: to call an async function you need to put `await` in front of it, like below:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
async def async_two():
|
async def async_two():
|
||||||
print("Hello from async_two!")
|
print("Hello from async_two!")
|
||||||
|
|
||||||
async def async_one():
|
async def async_one():
|
||||||
print("Hello from async_one!")
|
print("Hello from async_one!")
|
||||||
await async_two() # This is an async call
|
await async_two() # This is an async call
|
||||||
```
|
```
|
||||||
|
|
||||||
It has to be noted that using `await` outside of an async function is a `SyntaxError`, so basically async
|
It has to be noted that using `await` outside of an async function is a `SyntaxError`, so basically async
|
||||||
functions have a unique superpower: they, and no-one else, can call other async functions.
|
functions have a unique superpower: they, and no-one else, can call other async functions.
|
||||||
|
|
||||||
This already presents a chicken-and-egg problem, because when you fire up Python, it is running plain ol'
|
This already presents a chicken-and-egg problem, because when you fire up Python, it is running plain ol'
|
||||||
synchronous code; So how do we enter the async context in the first place?
|
synchronous code; So how do we enter the async context in the first place?
|
||||||
|
|
||||||
That is done via a special _synchronous function_, `giambio.run` in our case, that has the ability to call
|
That is done via a special _synchronous function_, `giambio.run` in our case, that has the ability to call
|
||||||
asynchronous functions and can therefore initiate the async context. For this
|
asynchronous functions and can therefore initiate the async context. For this
|
||||||
reason, `giambio.run` **must** be called from a synchronous context, to avoid a horrible _deadlock_.
|
reason, `giambio.run` **must** be called from a synchronous context, to avoid a horrible _deadlock_.
|
||||||
|
|
||||||
Now that you know all of this, you might be wondering why on earth would one use async functions instead of
|
Now that you know all of this, you might be wondering why on earth would one use async functions instead of
|
||||||
regular functions: after all, their ability to call other async functions seems pretty pointless in itself, doesn't it?
|
regular functions: after all, their ability to call other async functions seems pretty pointless in itself, doesn't it?
|
||||||
Take a look at this example below:
|
Take a look at this example below:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
async def foo():
|
async def foo():
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
|
|
||||||
giambio.run(foo) # Prints 'Hello, world!'
|
giambio.run(foo) # Prints 'Hello, world!'
|
||||||
```
|
```
|
||||||
|
|
||||||
This could as well be written the following way and would produce the same output:
|
This could as well be written the following way and would produce the same output:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def foo():
|
def foo():
|
||||||
print("Hello, world!")
|
print("Hello, world!")
|
||||||
|
|
||||||
foo() # Prints 'Hello, world!'
|
foo() # Prints 'Hello, world!'
|
||||||
```
|
```
|
||||||
|
|
||||||
To answer this question, we have to dig a bit deeper about _what_ giambio gives you in exchange for all this `async`/`await` madness.
|
To answer this question, we have to dig a bit deeper about _what_ giambio gives you in exchange for all this `async`/`await` madness.
|
||||||
|
|
||||||
We already introduced `giambio.run`, a special runner function that can start the async context from a synchronous one, but giambio
|
We already introduced `giambio.run`, a special runner function that can start the async context from a synchronous one, but giambio
|
||||||
provides also a set of tools, mainly for doing I/O. These functions, as you might have guessed, are async functions and they're useful!
|
provides also a set of tools, mainly for doing I/O. These functions, as you might have guessed, are async functions and they're useful!
|
||||||
So if you wanna take advantage of giambio, and hopefully you will after reading this guide, you need to write async code.
|
So if you wanna take advantage of giambio, and hopefully you will after reading this guide, you need to write async code.
|
||||||
As an example, take this function using `giambio.sleep` (`giambio.sleep` is like `time.sleep`, but with an async flavor):
|
As an example, take this function using `giambio.sleep` (`giambio.sleep` is like `time.sleep`, but with an async flavor):
|
||||||
|
|
||||||
__Side note__: If you have decent knowledge about asynchronous python, you might have noticed that we haven't mentioned coroutines
|
__Side note__: If you have decent knowledge about asynchronous python, you might have noticed that we haven't mentioned coroutines
|
||||||
so far. Don't worry, that is intentional: giambio never lets a user deal with coroutines on the surface because the whole async
|
so far. Don't worry, that is intentional: giambio never lets a user deal with coroutines on the surface because the whole async
|
||||||
model is much simpler if we take coroutines out of the game, and everything works just the same.
|
model is much simpler if we take coroutines out of the game, and everything works just the same.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
async def sleep_double(n):
|
async def sleep_double(n):
|
||||||
await giambio.sleep(2 * n)
|
await giambio.sleep(2 * n)
|
||||||
|
|
||||||
giambio.run(sleep_double, 2) # This hangs for 4 seconds and returns
|
giambio.run(sleep_double, 2) # This hangs for 4 seconds and returns
|
||||||
```
|
```
|
||||||
|
|
||||||
As it turns out, this function is one that's actually worth making async: because it calls another async function.
|
As it turns out, this function is one that's actually worth making async: because it calls another async function.
|
||||||
Not that there's nothing wrong with our `foo` from before, it surely works, but it doesn't really make sense to
|
Not that there's nothing wrong with our `foo` from before, it surely works, but it doesn't really make sense to
|
||||||
make it async in the first place.
|
make it async in the first place.
|
||||||
|
|
||||||
### Don't forget the `await`!
|
### Don't forget the `await`!
|
||||||
|
|
||||||
As we already learned, async functions can only be called with the `await` keyword, and it would be logical to
|
As we already learned, async functions can only be called with the `await` keyword, and it would be logical to
|
||||||
think that forgetting to do so would raise an error, but it's actually a little bit trickier than that.
|
think that forgetting to do so would raise an error, but it's actually a little bit trickier than that.
|
||||||
|
|
||||||
Take this example here:
|
Take this example here:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
async def sleep_double_broken(n):
|
async def sleep_double_broken(n):
|
||||||
print("Taking a nap!")
|
print("Taking a nap!")
|
||||||
start = giambio.clock()
|
start = giambio.clock()
|
||||||
giambio.sleep(2 * n) # We forgot the await!
|
giambio.sleep(2 * n) # We forgot the await!
|
||||||
end = giambio.clock() - start
|
end = giambio.clock() - start
|
||||||
print(f"Slept for {end:.2f} seconds!")
|
print(f"Slept for {end:.2f} seconds!")
|
||||||
|
|
||||||
giambio.run(sleep_double_broken, 2)
|
giambio.run(sleep_double_broken, 2)
|
||||||
```
|
```
|
||||||
|
|
||||||
Running this code, will produce an output that looks like this:
|
Running this code, will produce an output that looks like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
Taking a nap!
|
Taking a nap!
|
||||||
Slept 0.00 seconds!
|
Slept 0.00 seconds!
|
||||||
__main__:7: RuntimeWarning: coroutine 'sleep' was never awaited
|
__main__:7: RuntimeWarning: coroutine 'sleep' was never awaited
|
||||||
```
|
```
|
||||||
|
|
||||||
Wait, what happened here? From this output, it looks like the code worked, but something clearly went wrong:
|
Wait, what happened here? From this output, it looks like the code worked, but something clearly went wrong:
|
||||||
the function didn't sleep. Python gives us a hint that we broke _something_ by raising a warning, complaining
|
the function didn't sleep. Python gives us a hint that we broke _something_ by raising a warning, complaining
|
||||||
that `coroutine 'sleep' was never awaited` (you might not see this warning because it depends on whether a
|
that `coroutine 'sleep' was never awaited` (you might not see this warning because it depends on whether a
|
||||||
garbage collection cycle occurred or not).
|
garbage collection cycle occurred or not).
|
||||||
I know I said we weren't going to talk about coroutines, but you have to blame Python, not me. Just know that
|
I know I said we weren't going to talk about coroutines, but you have to blame Python, not me. Just know that
|
||||||
if you see a warning like that, it means that somewhere in your code you forgot an `await` when calling an async
|
if you see a warning like that, it means that somewhere in your code you forgot an `await` when calling an async
|
||||||
function, so try fixing that before trying to figure out what could be the problem if you have a long traceback:
|
function, so try fixing that before trying to figure out what could be the problem if you have a long traceback:
|
||||||
most likely that's just collateral damage caused by the missing keyword.
|
most likely that's just collateral damage caused by the missing keyword.
|
||||||
|
|
||||||
If you're ok with just remembering to put `await` every time you call an async function, you can safely skip to
|
If you're ok with just remembering to put `await` every time you call an async function, you can safely skip to
|
||||||
the next section, but for the curios among y'all I might as well explain exactly what happened there.
|
the next section, but for the curios among y'all I might as well explain exactly what happened there.
|
||||||
|
|
||||||
When coroutines are called without the `await`, they don't exactly do nothing: they return this weird 'coroutine'
|
When coroutines are called without the `await`, they don't exactly do nothing: they return this weird 'coroutine'
|
||||||
object
|
object
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
>>> giambio.sleep(1)
|
>>> giambio.sleep(1)
|
||||||
<coroutine object sleep at 0x1069520d0>
|
<coroutine object sleep at 0x1069520d0>
|
||||||
```
|
```
|
||||||
|
|
||||||
The reason for this is that while giambio tries to separate the async and sync worlds, therefore considering
|
The reason for this is that while giambio tries to separate the async and sync worlds, therefore considering
|
||||||
`await giambio.sleep(1)` as a single unit, when you `await` an async function Python does 2 things:
|
`await giambio.sleep(1)` as a single unit, when you `await` an async function Python does 2 things:
|
||||||
- It creates this weird coroutine object
|
- It creates this weird coroutine object
|
||||||
- Passes that object to `await`, which runs the function
|
- Passes that object to `await`, which runs the function
|
||||||
|
|
||||||
So basically that's why you always need to put `await` in front of an async function when calling it.
|
So basically that's why you always need to put `await` in front of an async function when calling it.
|
||||||
|
|
||||||
|
|
||||||
## Something actually useful
|
## Something actually useful
|
||||||
|
|
||||||
Ok, so far you've learned that asynchronous functions can call other async functions, and that giambio has a special
|
Ok, so far you've learned that asynchronous functions can call other async functions, and that giambio has a special
|
||||||
runner function that can start the whole async context, but we didn't really do anything _useful_.
|
runner function that can start the whole async context, but we didn't really do anything _useful_.
|
||||||
Our previous examples could be written using sync functions (like `time.sleep`) and they would work just fine, that isn't
|
Our previous examples could be written using sync functions (like `time.sleep`) and they would work just fine, that isn't
|
||||||
quite useful is it?
|
quite useful is it?
|
||||||
|
|
||||||
But here comes the reason why you would want to use a library like giambio: it can run multiple async functions __at the same time__.
|
But here comes the reason why you would want to use a library like giambio: it can run multiple async functions __at the same time__.
|
||||||
Yep, you read that right.
|
Yep, you read that right.
|
||||||
|
|
||||||
To demonstrate this, have a look a this example
|
To demonstrate this, have a look a this example
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
async def countdown(n: int):
|
async def countdown(n: int):
|
||||||
print(f"Counting down from {n}!")
|
print(f"Counting down from {n}!")
|
||||||
while n > 0:
|
while n > 0:
|
||||||
print(f"Down {n}")
|
print(f"Down {n}")
|
||||||
n -= 1
|
n -= 1
|
||||||
await giambio.sleep(1)
|
await giambio.sleep(1)
|
||||||
print("Countdown over")
|
print("Countdown over")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
async def countup(stop: int):
|
async def countup(stop: int):
|
||||||
print(f"Counting up to {stop}!")
|
print(f"Counting up to {stop}!")
|
||||||
x = 0
|
x = 0
|
||||||
while x < stop:
|
while x < stop:
|
||||||
print(f"Up {x}")
|
print(f"Up {x}")
|
||||||
x += 1
|
x += 1
|
||||||
await giambio.sleep(2)
|
await giambio.sleep(2)
|
||||||
print("Countup over")
|
print("Countup over")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
start = giambio.clock()
|
start = giambio.clock()
|
||||||
async with giambio.create_pool() as pool:
|
async with giambio.create_pool() as pool:
|
||||||
pool.spawn(countdown, 10)
|
pool.spawn(countdown, 10)
|
||||||
pool.spawn(countup, 5)
|
pool.spawn(countup, 5)
|
||||||
print("Children spawned, awaiting completion")
|
print("Children spawned, awaiting completion")
|
||||||
print(f"Task execution complete in {giambio.clock() - start:2f} seconds")
|
print(f"Task execution complete in {giambio.clock() - start:2f} seconds")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
giambio.run(main)
|
giambio.run(main)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is a lot going on here, and we'll explain every bit of it step by step:
|
There is a lot going on here, and we'll explain every bit of it step by step:
|
||||||
|
|
||||||
- First, we imported giambio and defined two async functions: `countup` and `countdown`
|
- First, we imported giambio and defined two async functions: `countup` and `countdown`
|
||||||
- These two functions do exactly what their name suggests, but for the purposes of
|
- These two functions do exactly what their name suggests, but for the purposes of
|
||||||
this tutorial, `countup` will be running twice as slow as `countdown` (see the call
|
this tutorial, `countup` will be running twice as slow as `countdown` (see the call
|
||||||
to `await giambio.sleep(2)`?)
|
to `await giambio.sleep(2)`?)
|
||||||
- Here comes the real fun: `async with`? What's going on there?
|
- Here comes the real fun: `async with`? What's going on there?
|
||||||
As it turns out, Python 3.5 didn't just add async functions, but also quite a bit
|
As it turns out, Python 3.5 didn't just add async functions, but also quite a bit
|
||||||
of related new syntax. One of the things that was added is asynchronous context managers.
|
of related new syntax. One of the things that was added is asynchronous context managers.
|
||||||
You might have already encountered context managers in python, but in case you didn't,
|
You might have already encountered context managers in python, but in case you didn't,
|
||||||
a line such as `with foo as sth` tells the Python interpreter to call `foo.__enter__()`
|
a line such as `with foo as sth` tells the Python interpreter to call `foo.__enter__()`
|
||||||
at the beginning of the block, and `foo.__exit__()` at the end of the block. The `as`
|
at the beginning of the block, and `foo.__exit__()` at the end of the block. The `as`
|
||||||
keyword just assigns the return value of `foo.__enter__()` to the variable `sth`. So
|
keyword just assigns the return value of `foo.__enter__()` to the variable `sth`. So
|
||||||
context managers are a shorthand for calling functions, and since Python 3.5 added
|
context managers are a shorthand for calling functions, and since Python 3.5 added
|
||||||
async functions, we also needed async context managers. While `with foo as sth` calls
|
async functions, we also needed async context managers. While `with foo as sth` calls
|
||||||
`foo.__enter__()`, `async with foo as sth` calls `await foo.__aenter__()`, easy huh?
|
`foo.__enter__()`, `async with foo as sth` calls `await foo.__aenter__()`, easy huh?
|
||||||
|
|
||||||
__Note__: On a related note, Python 3.5 also added asynchronous for loops! The logic is
|
__Note__: On a related note, Python 3.5 also added asynchronous for loops! The logic is
|
||||||
the same though: while `for item in container` calls `container.__next__()` to fetch the
|
the same though: while `for item in container` calls `container.__next__()` to fetch the
|
||||||
next item, `async for item in container` calls `await container.__anext__()` to do so.
|
next item, `async for item in container` calls `await container.__anext__()` to do so.
|
||||||
It's _that_ simple, mostly just remember to stick `await` everywhere and you'll be good.
|
It's _that_ simple, mostly just remember to stick `await` everywhere and you'll be good.
|
||||||
|
|
||||||
- Ok, so now we grasp `async with`, but what's with that `create_pool()`? In giambio,
|
- Ok, so now we grasp `async with`, but what's with that `create_pool()`? In giambio,
|
||||||
there are actually 2 ways to call async functions: one we've already seen (`await fn()`),
|
there are actually 2 ways to call async functions: one we've already seen (`await fn()`),
|
||||||
while the other is trough an asynchronous pool. The cool part about `pool.spawn()` is
|
while the other is trough an asynchronous pool. The cool part about `pool.spawn()` is
|
||||||
that it will return immediately, without waiting for the async function to finish. So,
|
that it will return immediately, without waiting for the async function to finish. So,
|
||||||
now our functions are running in the background.
|
now our functions are running in the background.
|
||||||
After we spawn our tasks, we hit the call to `print` and the end of the block, so Python
|
After we spawn our tasks, we hit the call to `print` and the end of the block, so Python
|
||||||
calls the pool's `__aexit__()` method. What this does is pause the parent task (our `main`
|
calls the pool's `__aexit__()` method. What this does is pause the parent task (our `main`
|
||||||
async function in this case) until all children task have exited, and as it turns out, that
|
async function in this case) until all children task have exited, and as it turns out, that
|
||||||
is a good thing.
|
is a good thing.
|
||||||
The reason why pools always wait for all children to have finished executing is that it makes
|
The reason why pools always wait for all children to have finished executing is that it makes
|
||||||
easier propagating exceptions in the parent if something goes wrong: unlike many other frameworks,
|
easier propagating exceptions in the parent if something goes wrong: unlike many other frameworks,
|
||||||
exceptions in giambio always behave as expected
|
exceptions in giambio always behave as expected
|
||||||
|
|
||||||
|
|
||||||
Ok, so, let's try running this snippet and see what we get:
|
Ok, so, let's try running this snippet and see what we get:
|
||||||
|
|
||||||
```
|
```
|
||||||
Children spawned, awaiting completion
|
Children spawned, awaiting completion
|
||||||
Counting down from 10!
|
Counting down from 10!
|
||||||
Down 10
|
Down 10
|
||||||
Counting up to 5!
|
Counting up to 5!
|
||||||
Up 0
|
Up 0
|
||||||
Down 9
|
Down 9
|
||||||
Up 1
|
Up 1
|
||||||
Down 8
|
Down 8
|
||||||
Down 7
|
Down 7
|
||||||
Up 2
|
Up 2
|
||||||
Down 6
|
Down 6
|
||||||
Down 5
|
Down 5
|
||||||
Up 3
|
Up 3
|
||||||
Down 4
|
Down 4
|
||||||
Down 3
|
Down 3
|
||||||
Up 4
|
Up 4
|
||||||
Down 2
|
Down 2
|
||||||
Down 1
|
Down 1
|
||||||
Countup over
|
Countup over
|
||||||
Countdown over
|
Countdown over
|
||||||
Task execution complete in 10.07 seconds
|
Task execution complete in 10.07 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
(Your output might have some lines swapped compared to this)
|
(Your output might have some lines swapped compared to this)
|
||||||
|
|
||||||
You see how `countup` and `countdown` both start and finish
|
You see how `countup` and `countdown` both start and finish
|
||||||
together? Moreover, even though each function slept for about 10
|
together? Moreover, even though each function slept for about 10
|
||||||
seconds (therefore 20 seconds total), the program just took 10
|
seconds (therefore 20 seconds total), the program just took 10
|
||||||
seconds to complete, so our children are really running at the same time.
|
seconds to complete, so our children are really running at the same time.
|
||||||
|
|
||||||
If you've ever done thread programming, this will feel like home, and that's good:
|
If you've ever done thread programming, this will feel like home, and that's good:
|
||||||
that's exactly what we want. But beware! No threads are involved here, giambio is
|
that's exactly what we want. But beware! No threads are involved here, giambio is
|
||||||
running in a single thread. That's why we talked about _tasks_ rather than _threads_
|
running in a single thread. That's why we talked about _tasks_ rather than _threads_
|
||||||
so far. The difference between the two is that you can run a lot of tasks in a single
|
so far. The difference between the two is that you can run a lot of tasks in a single
|
||||||
thread, and that with threads Python can switch which thread is running at any time.
|
thread, and that with threads Python can switch which thread is running at any time.
|
||||||
Giambio, on the other hand, can switch tasks only at certain fixed points called
|
Giambio, on the other hand, can switch tasks only at certain fixed points called
|
||||||
_checkpoints_, more on that later.
|
_checkpoints_, more on that later.
|
||||||
|
|
||||||
### A sneak peak into the async world
|
### A sneak peak into the async world
|
||||||
|
|
||||||
The basic idea behind libraries like giambio is that they can run a lot of tasks
|
The basic idea behind libraries like giambio is that they can run a lot of tasks
|
||||||
at the same time by switching back and forth between them at appropriate places.
|
at the same time by switching back and forth between them at appropriate places.
|
||||||
An example for that could be a web server: while the server is waiting for a response
|
An example for that could be a web server: while the server is waiting for a response
|
||||||
from a client, we can accept another connection. You don't necessarily need all these
|
from a client, we can accept another connection. You don't necessarily need all these
|
||||||
pesky details to use giambio, but it's good to have at least an high-level understanding
|
pesky details to use giambio, but it's good to have at least an high-level understanding
|
||||||
of how this all works.
|
of how this all works.
|
||||||
|
|
||||||
The peculiarity of asynchronous functions is that they can suspend their execution: that's
|
The peculiarity of asynchronous functions is that they can suspend their execution: that's
|
||||||
what `await` does, it yields back the execution control to giambio, which can then decide
|
what `await` does, it yields back the execution control to giambio, which can then decide
|
||||||
what to do next.
|
what to do next.
|
||||||
|
|
||||||
To understand this better, take a look at this code:
|
To understand this better, take a look at this code:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def countdown(n: int) -> int:
|
def countdown(n: int) -> int:
|
||||||
while n:
|
while n:
|
||||||
yield n
|
yield n
|
||||||
n -= 1
|
n -= 1
|
||||||
|
|
||||||
for x in countdown(5):
|
for x in countdown(5):
|
||||||
print(x)
|
print(x)
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above snippet, `countdown` is a generator function. Generators are really useful because
|
In the above snippet, `countdown` is a generator function. Generators are really useful because
|
||||||
they allow to customize iteration. Running that code produces the following output:
|
they allow to customize iteration. Running that code produces the following output:
|
||||||
|
|
||||||
```
|
```
|
||||||
5
|
5
|
||||||
4
|
4
|
||||||
3
|
3
|
||||||
2
|
2
|
||||||
1
|
1
|
||||||
```
|
```
|
||||||
|
|
||||||
The trick for this to work is `yield`.
|
The trick for this to work is `yield`.
|
||||||
What `yield` does is return back to the caller and suspend itself: In our case, `yield`
|
What `yield` does is return back to the caller and suspend itself: In our case, `yield`
|
||||||
returns to the for loop, which calls `countdown` again. So, the generator resumes right
|
returns to the for loop, which calls `countdown` again. So, the generator resumes right
|
||||||
after the `yield`, decrements n, and loops right back to the top for the while loop to
|
after the `yield`, decrements n, and loops right back to the top for the while loop to
|
||||||
execute again. It's that suspension part that allows the async magic to happen: the whole
|
execute again. It's that suspension part that allows the async magic to happen: the whole
|
||||||
`async`/`await` logic overlaps a lot with generator functions.
|
`async`/`await` logic overlaps a lot with generator functions.
|
||||||
|
|
||||||
Some libraries, like `asyncio`, take advantage of this yielding mechanism, because they were made
|
Some libraries, like `asyncio`, take advantage of this yielding mechanism, because they were made
|
||||||
way before Python 3.5 added that nice new syntax.
|
way before Python 3.5 added that nice new syntax.
|
||||||
|
|
||||||
So, since only async functions can suspend themselves, the only places where giambio will switch
|
So, since only async functions can suspend themselves, the only places where giambio will switch
|
||||||
tasks is where there is a call to `await something()`. If there is no `await`, then you can be sure
|
tasks is where there is a call to `await something()`. If there is no `await`, then you can be sure
|
||||||
that giambio will not switch tasks (because it can't): this makes the asynchronous model much easier
|
that giambio will not switch tasks (because it can't): this makes the asynchronous model much easier
|
||||||
to reason about, because you can know if a function will ever switch, and where will it do so, just
|
to reason about, because you can know if a function will ever switch, and where will it do so, just
|
||||||
by looking at its source code. That is very different from what threads do: they can (and will) switch
|
by looking at its source code. That is very different from what threads do: they can (and will) switch
|
||||||
whenever they feel like it.
|
whenever they feel like it.
|
||||||
|
|
||||||
Remember when we talked about checkpoints? That's what they are: calls to async functions that allow
|
Remember when we talked about checkpoints? That's what they are: calls to async functions that allow
|
||||||
giambio to switch tasks. The problem with checkpoints is that if you don't have enough of them in your code,
|
giambio to switch tasks. The problem with checkpoints is that if you don't have enough of them in your code,
|
||||||
then giambio will switch less frequently, hurting concurrency. It turns out that a quick and easy fix
|
then giambio will switch less frequently, hurting concurrency. It turns out that a quick and easy fix
|
||||||
for that is calling `await giambio.sleep(0)`; This will implicitly let giambio kick in and do its job,
|
for that is calling `await giambio.sleep(0)`; This will implicitly let giambio kick in and do its job,
|
||||||
and it will reschedule the caller almost immediately, because the sleep time is 0.
|
and it will reschedule the caller almost immediately, because the sleep time is 0.
|
||||||
|
|
||||||
### Mix and match? No thanks
|
### Mix and match? No thanks
|
||||||
|
|
||||||
You may wonder whether you can mix async libraries: for instance, can we call `trio.sleep` in a
|
You may wonder whether you can mix async libraries: for instance, can we call `trio.sleep` in a
|
||||||
giambio application? The answer is no, we can't, and there's a reason for that. Giambio wraps all
|
giambio application? The answer is no, we can't, and there's a reason for that. Giambio wraps all
|
||||||
your asynchronous code in its event loop, which is what actually runs the tasks. When you call
|
your asynchronous code in its event loop, which is what actually runs the tasks. When you call
|
||||||
`await giambio.something()`, what you're doing is sending "commands" to the event loop asking it
|
`await giambio.something()`, what you're doing is sending "commands" to the event loop asking it
|
||||||
to perform a certain thing in a given task, and to communicate your intent to the loop, the
|
to perform a certain thing in a given task, and to communicate your intent to the loop, the
|
||||||
primitives (such as `giambio.sleep`) talk a language that only giambio's event loop can understand.
|
primitives (such as `giambio.sleep`) talk a language that only giambio's event loop can understand.
|
||||||
Other libraries have other private "languages", so mixing them is not possible: doing so will cause
|
Other libraries have other private "languages", so mixing them is not possible: doing so will cause
|
||||||
giambio to get very confused and most likely just explode spectacularly badly
|
giambio to get very confused and most likely just explode spectacularly badly
|
||||||
|
|
||||||
|
|
||||||
TODO: I/O
|
TODO: I/O
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This is a relatively young project and it is looking for collaborators! It's not rocket science,
|
This is a relatively young project and it is looking for collaborators! It's not rocket science,
|
||||||
but writing a proper framework like this implies some non-trivial issues that require proper and optimized solutions,
|
but writing a proper framework like this implies some non-trivial issues that require proper and optimized solutions,
|
||||||
so if you feel like you want to challenge yourself don't hesitate to contact me on [Telegram](https://telegram.me/nocturn9x)
|
so if you feel like you want to challenge yourself don't hesitate to contact me on [Telegram](https://telegram.me/nocturn9x)
|
||||||
or by [E-mail](mailto:hackhab@gmail.com)
|
or by [E-mail](mailto:hackhab@gmail.com)
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
"""
|
"""
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Nocturn9x aka Isgiambyy"
|
__author__ = "Nocturn9x aka Isgiambyy"
|
||||||
__version__ = (1, 0, 0)
|
__version__ = (1, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from .traps import sleep, current_task
|
from .traps import sleep, current_task
|
||||||
from .objects import Event
|
from .objects import Event
|
||||||
from .run import run, clock, wrap_socket, create_pool, get_event_loop, new_event_loop
|
from .run import run, clock, wrap_socket, create_pool, get_event_loop, new_event_loop
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"exceptions",
|
"exceptions",
|
||||||
"sleep",
|
"sleep",
|
||||||
"Event",
|
"Event",
|
||||||
"run",
|
"run",
|
||||||
"clock",
|
"clock",
|
||||||
"wrap_socket",
|
"wrap_socket",
|
||||||
"create_pool",
|
"create_pool",
|
||||||
"get_event_loop",
|
"get_event_loop",
|
||||||
"current_task",
|
"current_task",
|
||||||
"new_event_loop"
|
"new_event_loop"
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,69 +1,70 @@
|
||||||
"""
|
"""
|
||||||
Higher-level context managers for async pools
|
Higher-level context managers for async pools
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import types
|
import types
|
||||||
from .core import AsyncScheduler
|
from .core import AsyncScheduler
|
||||||
from .objects import Task
|
from .objects import Task
|
||||||
|
|
||||||
|
|
||||||
class TaskManager:
|
class TaskManager:
|
||||||
"""
|
"""
|
||||||
An asynchronous context manager for giambio
|
An asynchronous context manager for giambio
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, loop: AsyncScheduler) -> None:
|
def __init__(self, loop: AsyncScheduler) -> None:
|
||||||
"""
|
"""
|
||||||
Object constructor
|
Object constructor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.tasks = []
|
self.tasks = []
|
||||||
|
|
||||||
def spawn(self, func: types.FunctionType, *args):
|
def spawn(self, func: types.FunctionType, *args):
|
||||||
"""
|
"""
|
||||||
Spawns a child task
|
Spawns a child task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
task = Task(func(*args), func.__name__ or str(func))
|
task = Task(func(*args), func.__name__ or str(func))
|
||||||
task.parent = self.loop.current_task
|
task.parent = self.loop.current_task
|
||||||
self.loop.tasks.append(task)
|
self.loop.tasks.append(task)
|
||||||
self.tasks.append(task)
|
self.tasks.append(task)
|
||||||
|
|
||||||
def spawn_after(self, func: types.FunctionType, n: int, *args):
|
def spawn_after(self, func: types.FunctionType, n: int, *args):
|
||||||
"""
|
"""
|
||||||
Schedules a task for execution after n seconds
|
Schedules a task for execution after n seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert n >= 0, "The time delay can't be negative"
|
assert n >= 0, "The time delay can't be negative"
|
||||||
task = Task(func(*args), func.__name__ or str(func))
|
task = Task(func(*args), func.__name__ or str(func))
|
||||||
task.parent = self.loop.current_task
|
task.parent = self.loop.current_task
|
||||||
self.loop.paused.put(task, n)
|
self.loop.paused.put(task, n)
|
||||||
self.tasks.append(task)
|
self.tasks.append(task)
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb):
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
for task in self.tasks:
|
for task in self.tasks:
|
||||||
try:
|
try:
|
||||||
await task.join()
|
await task.join()
|
||||||
except BaseException as e:
|
except BaseException:
|
||||||
self.tasks.remove(task)
|
self.tasks.remove(task)
|
||||||
for to_cancel in self.tasks:
|
for to_cancel in self.tasks:
|
||||||
await to_cancel.cancel()
|
await to_cancel.cancel()
|
||||||
|
print("oof")
|
796
giambio/core.py
796
giambio/core.py
|
@ -1,398 +1,398 @@
|
||||||
"""
|
"""
|
||||||
The main runtime environment for giambio
|
The main runtime environment for giambio
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Import libraries and internal resources
|
# Import libraries and internal resources
|
||||||
import types
|
import types
|
||||||
import socket
|
import socket
|
||||||
from time import sleep as wait
|
from time import sleep as wait
|
||||||
from timeit import default_timer
|
from timeit import default_timer
|
||||||
from .objects import Task, TimeQueue
|
from .objects import Task, TimeQueue
|
||||||
from socket import SOL_SOCKET, SO_ERROR
|
from socket import SOL_SOCKET, SO_ERROR
|
||||||
from .traps import want_read, want_write
|
from .traps import want_read, want_write
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from .socket import AsyncSocket, WantWrite, WantRead
|
from .socket import AsyncSocket, WantWrite, WantRead
|
||||||
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
|
||||||
from .exceptions import (InternalError,
|
from .exceptions import (InternalError,
|
||||||
CancelledError,
|
CancelledError,
|
||||||
ResourceBusy,
|
ResourceBusy,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncScheduler:
|
class AsyncScheduler:
|
||||||
"""
|
"""
|
||||||
An asynchronous scheduler implementation. Tries to mimic the threaded
|
An asynchronous scheduler implementation. Tries to mimic the threaded
|
||||||
model in its simplicity, without using actual threads, but rather alternating
|
model in its simplicity, without using actual threads, but rather alternating
|
||||||
across coroutines execution to let more than one thing at a time to proceed
|
across coroutines execution to let more than one thing at a time to proceed
|
||||||
with its calculations. An attempt to fix the threaded model has been made
|
with its calculations. An attempt to fix the threaded model has been made
|
||||||
without making the API unnecessarily complicated.
|
without making the API unnecessarily complicated.
|
||||||
A few examples are tasks cancellation and exception propagation.
|
A few examples are tasks cancellation and exception propagation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Object constructor
|
Object constructor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Tasks that are ready to run
|
# Tasks that are ready to run
|
||||||
self.tasks = deque()
|
self.tasks = deque()
|
||||||
# Selector object to perform I/O multiplexing
|
# Selector object to perform I/O multiplexing
|
||||||
self.selector = DefaultSelector()
|
self.selector = DefaultSelector()
|
||||||
# This will always point to the currently running coroutine (Task object)
|
# This will always point to the currently running coroutine (Task object)
|
||||||
self.current_task = None
|
self.current_task = None
|
||||||
# Monotonic clock to keep track of elapsed time reliably
|
# Monotonic clock to keep track of elapsed time reliably
|
||||||
self.clock = default_timer
|
self.clock = default_timer
|
||||||
# Tasks that are asleep
|
# Tasks that are asleep
|
||||||
self.paused = TimeQueue(self.clock)
|
self.paused = TimeQueue(self.clock)
|
||||||
# All active Event objects
|
# All active Event objects
|
||||||
self.events = set()
|
self.events = set()
|
||||||
# Data to send back to a trap
|
# Data to send back to a trap
|
||||||
self.to_send = None
|
self.to_send = None
|
||||||
# Have we ever ran?
|
# Have we ever ran?
|
||||||
self.has_ran = False
|
self.has_ran = False
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
"""
|
"""
|
||||||
Returns True if there is work to do
|
Returns True if there is work to do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.selector.get_map() or any([self.paused,
|
if self.selector.get_map() or any([self.paused,
|
||||||
self.tasks,
|
self.tasks,
|
||||||
self.events
|
self.events
|
||||||
]):
|
]):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""
|
"""
|
||||||
Shuts down the event loop
|
Shuts down the event loop
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: See if other teardown is required (massive join()?)
|
# TODO: See if other teardown is required (massive join()?)
|
||||||
self.selector.close()
|
self.selector.close()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
Starts the loop and 'listens' for events until there is work to do,
|
Starts the loop and 'listens' for events until there is work to do,
|
||||||
then exits. This behavior kinda reflects a kernel, as coroutines can
|
then exits. This behavior kinda reflects a kernel, as coroutines can
|
||||||
request the loop's functionality only trough some fixed entry points,
|
request the loop's functionality only trough some fixed entry points,
|
||||||
which in turn yield and give execution control to the loop itself.
|
which in turn yield and give execution control to the loop itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if self.done():
|
if self.done():
|
||||||
self.shutdown()
|
self.shutdown()
|
||||||
break
|
break
|
||||||
elif not self.tasks:
|
elif not self.tasks:
|
||||||
if self.paused:
|
if self.paused:
|
||||||
# If there are no actively running tasks
|
# If there are no actively running tasks
|
||||||
# we try to schedule the asleep ones
|
# we try to schedule the asleep ones
|
||||||
self.awake_sleeping()
|
self.awake_sleeping()
|
||||||
if self.selector.get_map():
|
if self.selector.get_map():
|
||||||
# The next step is checking for I/O
|
# The next step is checking for I/O
|
||||||
self.check_io()
|
self.check_io()
|
||||||
if self.events:
|
if self.events:
|
||||||
# Try to awake event-waiting tasks
|
# Try to awake event-waiting tasks
|
||||||
self.check_events()
|
self.check_events()
|
||||||
# While there are tasks to run
|
# While there are tasks to run
|
||||||
while self.tasks:
|
while self.tasks:
|
||||||
# Sets the currently running task
|
# Sets the currently running task
|
||||||
self.current_task = self.tasks.popleft()
|
self.current_task = self.tasks.popleft()
|
||||||
if self.current_task.cancel_pending:
|
if self.current_task.cancel_pending:
|
||||||
self.do_cancel()
|
self.do_cancel()
|
||||||
if self.to_send and self.current_task.status != "init":
|
if self.to_send and self.current_task.status != "init":
|
||||||
data = self.to_send
|
data = self.to_send
|
||||||
else:
|
else:
|
||||||
data = None
|
data = None
|
||||||
# Run a single step with the calculation
|
# Run a single step with the calculation
|
||||||
method, *args = self.current_task.run(data)
|
method, *args = self.current_task.run(data)
|
||||||
self.current_task.status = "run"
|
self.current_task.status = "run"
|
||||||
self.current_task.steps += 1
|
self.current_task.steps += 1
|
||||||
# Data has been sent, reset it to None
|
# Data has been sent, reset it to None
|
||||||
if self.to_send and self.current_task != "init":
|
if self.to_send and self.current_task != "init":
|
||||||
self.to_send = None
|
self.to_send = None
|
||||||
# Sneaky method call, thanks to David Beazley for this ;)
|
# Sneaky method call, thanks to David Beazley for this ;)
|
||||||
getattr(self, method)(*args)
|
getattr(self, method)(*args)
|
||||||
except AttributeError: # If this happens, that's quite bad!
|
except AttributeError: # If this happens, that's quite bad!
|
||||||
raise InternalError("Uh oh! Something very bad just happened, did"
|
raise InternalError("Uh oh! Something very bad just happened, did"
|
||||||
" you try to mix primitives from other async libraries?") from None
|
" you try to mix primitives from other async libraries?") from None
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
self.current_task.status = "cancelled"
|
self.current_task.status = "cancelled"
|
||||||
self.current_task.cancelled = True
|
self.current_task.cancelled = True
|
||||||
self.current_task.cancel_pending = False
|
self.current_task.cancel_pending = False
|
||||||
self.join() # TODO: Investigate if a call to join() is needed
|
self.join() # TODO: Investigate if a call to join() is needed
|
||||||
except StopIteration as ret:
|
except StopIteration as ret:
|
||||||
# Coroutine ends
|
# Coroutine ends
|
||||||
self.current_task.status = "end"
|
self.current_task.status = "end"
|
||||||
self.current_task.result = ret.value
|
self.current_task.result = ret.value
|
||||||
self.current_task.finished = True
|
self.current_task.finished = True
|
||||||
self.join()
|
self.join()
|
||||||
except BaseException as err:
|
except BaseException as err:
|
||||||
self.current_task.exc = err
|
self.current_task.exc = err
|
||||||
self.current_task.status = "crashed"
|
self.current_task.status = "crashed"
|
||||||
self.join()
|
self.join()
|
||||||
|
|
||||||
def do_cancel(self):
|
def do_cancel(self):
|
||||||
"""
|
"""
|
||||||
Performs task cancellation by throwing CancelledError inside the current
|
Performs task cancellation by throwing CancelledError inside the current
|
||||||
task in order to stop it from executing. The loop continues to execute
|
task in order to stop it from executing. The loop continues to execute
|
||||||
as tasks are independent
|
as tasks are independent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Do we need anything else?
|
# TODO: Do we need anything else?
|
||||||
self.current_task.throw(CancelledError)
|
self.current_task.throw(CancelledError)
|
||||||
|
|
||||||
def get_running(self):
|
def get_running(self):
|
||||||
"""
|
"""
|
||||||
Returns the current task
|
Returns the current task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.tasks.append(self.current_task)
|
self.tasks.append(self.current_task)
|
||||||
self.to_send = self.current_task
|
self.to_send = self.current_task
|
||||||
|
|
||||||
def check_events(self):
|
def check_events(self):
|
||||||
"""
|
"""
|
||||||
Checks for ready or expired events and triggers them
|
Checks for ready or expired events and triggers them
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for event in self.events.copy():
|
for event in self.events.copy():
|
||||||
if event.set:
|
if event.set:
|
||||||
event.event_caught = True
|
event.event_caught = True
|
||||||
event.waiters
|
event.waiters
|
||||||
self.tasks.extend(event.waiters)
|
self.tasks.extend(event.waiters)
|
||||||
self.events.remove(event)
|
self.events.remove(event)
|
||||||
|
|
||||||
def awake_sleeping(self):
|
def awake_sleeping(self):
|
||||||
"""
|
"""
|
||||||
Checks for and reschedules sleeping tasks
|
Checks for and reschedules sleeping tasks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
wait(max(0.0, self.paused[0][0] - self.clock()))
|
wait(max(0.0, self.paused[0][0] - self.clock()))
|
||||||
# Sleep until the closest deadline in order not to waste CPU cycles
|
# Sleep until the closest deadline in order not to waste CPU cycles
|
||||||
while self.paused[0][0] < self.clock():
|
while self.paused[0][0] < self.clock():
|
||||||
# Reschedules tasks when their deadline has elapsed
|
# Reschedules tasks when their deadline has elapsed
|
||||||
self.tasks.append(self.paused.get())
|
self.tasks.append(self.paused.get())
|
||||||
if not self.paused:
|
if not self.paused:
|
||||||
break
|
break
|
||||||
|
|
||||||
def check_io(self):
|
def check_io(self):
|
||||||
"""
|
"""
|
||||||
Checks and schedules task to perform I/O
|
Checks and schedules task to perform I/O
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.tasks or self.events: # If there are tasks or events, never wait
|
if self.tasks or self.events: # If there are tasks or events, never wait
|
||||||
timeout = 0.0
|
timeout = 0.0
|
||||||
elif self.paused: # If there are asleep tasks, wait until the closest
|
elif self.paused: # If there are asleep tasks, wait until the closest
|
||||||
# deadline
|
# deadline
|
||||||
timeout = max(0.0, self.paused[0][0] - self.clock())
|
timeout = max(0.0, self.paused[0][0] - self.clock())
|
||||||
else:
|
else:
|
||||||
timeout = None # If we _only_ have I/O to do, then wait indefinitely
|
timeout = None # If we _only_ have I/O to do, then wait indefinitely
|
||||||
for key in dict(self.selector.get_map()).values():
|
for key in dict(self.selector.get_map()).values():
|
||||||
# We make sure we don't reschedule finished tasks
|
# We make sure we don't reschedule finished tasks
|
||||||
if key.data.finished:
|
if key.data.finished:
|
||||||
key.data.last_io = ()
|
key.data.last_io = ()
|
||||||
self.selector.unregister(key.fileobj)
|
self.selector.unregister(key.fileobj)
|
||||||
if self.selector.get_map(): # If there is indeed tasks waiting on I/O
|
if self.selector.get_map(): # If there is indeed tasks waiting on I/O
|
||||||
io_ready = self.selector.select(timeout)
|
io_ready = self.selector.select(timeout)
|
||||||
# Get sockets that are ready and schedule their tasks
|
# Get sockets that are ready and schedule their tasks
|
||||||
for key, _ in io_ready:
|
for key, _ in io_ready:
|
||||||
self.tasks.append(key.data) # Resource ready? Schedule its task
|
self.tasks.append(key.data) # Resource ready? Schedule its task
|
||||||
|
|
||||||
def start(self, func: types.FunctionType, *args):
|
def start(self, func: types.FunctionType, *args):
|
||||||
"""
|
"""
|
||||||
Starts the event loop from a sync context
|
Starts the event loop from a sync context
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = Task(func(*args), func.__name__ or str(func))
|
entry = Task(func(*args), func.__name__ or str(func))
|
||||||
self.tasks.append(entry)
|
self.tasks.append(entry)
|
||||||
self.run()
|
self.run()
|
||||||
self.has_ran = True
|
self.has_ran = True
|
||||||
if entry.exc:
|
if entry.exc:
|
||||||
raise entry.exc from None
|
raise entry.exc from None
|
||||||
|
|
||||||
def reschedule_joinee(self):
|
def reschedule_joinee(self):
|
||||||
"""
|
"""
|
||||||
Reschedules the joinee(s) task of the
|
Reschedules the joinee(s) of the
|
||||||
currently running task, if any
|
currently running task, if any
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.tasks.extend(self.current_task.waiters)
|
self.tasks.extend(self.current_task.waiters)
|
||||||
|
|
||||||
def join(self):
|
def join(self):
|
||||||
"""
|
"""
|
||||||
Handler for the 'join' event, does some magic to tell the scheduler
|
Handler for the 'join' event, does some magic to tell the scheduler
|
||||||
to wait until the current coroutine ends
|
to wait until the current coroutine ends
|
||||||
"""
|
"""
|
||||||
|
|
||||||
child = self.current_task
|
child = self.current_task
|
||||||
child.joined = True
|
child.joined = True
|
||||||
if child.parent:
|
if child.parent:
|
||||||
child.waiters.append(child.parent)
|
child.waiters.append(child.parent)
|
||||||
if child.finished:
|
if child.finished:
|
||||||
self.reschedule_joinee()
|
self.reschedule_joinee()
|
||||||
elif child.exc:
|
elif child.exc:
|
||||||
... # TODO: Handle exceptions
|
... # TODO: Handle exceptions
|
||||||
|
|
||||||
def sleep(self, seconds: int or float):
|
def sleep(self, seconds: int or float):
|
||||||
"""
|
"""
|
||||||
Puts the caller to sleep for a given amount of seconds
|
Puts the caller to sleep for a given amount of seconds
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if seconds:
|
if seconds:
|
||||||
self.current_task.status = "sleep"
|
self.current_task.status = "sleep"
|
||||||
self.paused.put(self.current_task, seconds)
|
self.paused.put(self.current_task, seconds)
|
||||||
else:
|
else:
|
||||||
self.tasks.append(self.current_task)
|
self.tasks.append(self.current_task)
|
||||||
|
|
||||||
# TODO: More generic I/O rather than just sockets
|
# TODO: More generic I/O rather than just sockets
|
||||||
def want_read(self, sock: socket.socket):
|
def want_read(self, sock: socket.socket):
|
||||||
"""
|
"""
|
||||||
Handler for the 'want_read' event, registers the socket inside the
|
Handler for the 'want_read' event, registers the socket inside the
|
||||||
selector to perform I/0 multiplexing
|
selector to perform I/0 multiplexing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.current_task.status = "I/O"
|
self.current_task.status = "I/O"
|
||||||
if self.current_task.last_io:
|
if self.current_task.last_io:
|
||||||
if self.current_task.last_io == ("READ", sock):
|
if self.current_task.last_io == ("READ", sock):
|
||||||
# Socket is already scheduled!
|
# Socket is already scheduled!
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.selector.unregister(sock)
|
self.selector.unregister(sock)
|
||||||
self.current_task.last_io = "READ", sock
|
self.current_task.last_io = "READ", sock
|
||||||
try:
|
try:
|
||||||
self.selector.register(sock, EVENT_READ, self.current_task)
|
self.selector.register(sock, EVENT_READ, self.current_task)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# The socket is already registered doing something else
|
# The socket is already registered doing something else
|
||||||
raise ResourceBusy("The given resource is busy!") from None
|
raise ResourceBusy("The given resource is busy!") from None
|
||||||
|
|
||||||
def want_write(self, sock: socket.socket):
|
def want_write(self, sock: socket.socket):
|
||||||
"""
|
"""
|
||||||
Handler for the 'want_write' event, registers the socket inside the
|
Handler for the 'want_write' event, registers the socket inside the
|
||||||
selector to perform I/0 multiplexing
|
selector to perform I/0 multiplexing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.current_task.status = "I/O"
|
self.current_task.status = "I/O"
|
||||||
if self.current_task.last_io:
|
if self.current_task.last_io:
|
||||||
if self.current_task.last_io == ("WRITE", sock):
|
if self.current_task.last_io == ("WRITE", sock):
|
||||||
# Socket is already scheduled!
|
# Socket is already scheduled!
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# TODO: Inspect why modify() causes issues
|
# TODO: Inspect why modify() causes issues
|
||||||
self.selector.unregister(sock)
|
self.selector.unregister(sock)
|
||||||
self.current_task.last_io = "WRITE", sock
|
self.current_task.last_io = "WRITE", sock
|
||||||
try:
|
try:
|
||||||
self.selector.register(sock, EVENT_WRITE, self.current_task)
|
self.selector.register(sock, EVENT_WRITE, self.current_task)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ResourceBusy("The given resource is busy!") from None
|
raise ResourceBusy("The given resource is busy!") from None
|
||||||
|
|
||||||
def event_set(self, event):
|
def event_set(self, event):
|
||||||
"""
|
"""
|
||||||
Sets an event
|
Sets an event
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.events.add(event)
|
self.events.add(event)
|
||||||
event.waiters.append(self.current_task)
|
event.waiters.append(self.current_task)
|
||||||
event.set = True
|
event.set = True
|
||||||
self.reschedule_joinee()
|
self.reschedule_joinee()
|
||||||
|
|
||||||
def event_wait(self, event):
|
def event_wait(self, event):
|
||||||
"""
|
"""
|
||||||
Pauses the current task on an event
|
Pauses the current task on an event
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event.waiters.append(self.current_task)
|
event.waiters.append(self.current_task)
|
||||||
|
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
"""
|
"""
|
||||||
Handler for the 'cancel' event, schedules the task to be cancelled later
|
Handler for the 'cancel' event, schedules the task to be cancelled later
|
||||||
or does so straight away if it is safe to do so
|
or does so straight away if it is safe to do so
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.current_task.status in ("I/O", "sleep"):
|
if self.current_task.status in ("I/O", "sleep"):
|
||||||
# We cancel right away
|
# We cancel right away
|
||||||
self.do_cancel()
|
self.do_cancel()
|
||||||
else:
|
else:
|
||||||
self.current_task.cancel_pending = True # Cancellation is deferred
|
self.current_task.cancel_pending = True # Cancellation is deferred
|
||||||
|
|
||||||
def wrap_socket(self, sock):
|
def wrap_socket(self, sock):
|
||||||
"""
|
"""
|
||||||
Wraps a standard socket into an AsyncSocket object
|
Wraps a standard socket into an AsyncSocket object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return AsyncSocket(sock, self)
|
return AsyncSocket(sock, self)
|
||||||
|
|
||||||
async def read_sock(self, sock: socket.socket, buffer: int):
|
async def read_sock(self, sock: socket.socket, buffer: int):
|
||||||
"""
|
"""
|
||||||
Reads from a socket asynchronously, waiting until the resource is
|
Reads from a socket asynchronously, waiting until the resource is
|
||||||
available and returning up to buffer bytes from the socket
|
available and returning up to buffer bytes from the socket
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return sock.recv(buffer)
|
return sock.recv(buffer)
|
||||||
except WantRead:
|
except WantRead:
|
||||||
await want_read(sock)
|
await want_read(sock)
|
||||||
return sock.recv(buffer)
|
return sock.recv(buffer)
|
||||||
|
|
||||||
async def accept_sock(self, sock: socket.socket):
|
async def accept_sock(self, sock: socket.socket):
|
||||||
"""
|
"""
|
||||||
Accepts a socket connection asynchronously, waiting until the resource
|
Accepts a socket connection asynchronously, waiting until the resource
|
||||||
is available and returning the result of the accept() call
|
is available and returning the result of the accept() call
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return sock.accept()
|
return sock.accept()
|
||||||
except WantRead:
|
except WantRead:
|
||||||
await want_read(sock)
|
await want_read(sock)
|
||||||
return sock.accept()
|
return sock.accept()
|
||||||
|
|
||||||
async def sock_sendall(self, sock: socket.socket, data: bytes):
|
async def sock_sendall(self, sock: socket.socket, data: bytes):
|
||||||
"""
|
"""
|
||||||
Sends all the passed data, as bytes, trough the socket asynchronously
|
Sends all the passed data, as bytes, trough the socket asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while data:
|
while data:
|
||||||
try:
|
try:
|
||||||
sent_no = sock.send(data)
|
sent_no = sock.send(data)
|
||||||
except WantWrite:
|
except WantWrite:
|
||||||
await want_write(sock)
|
await want_write(sock)
|
||||||
sent_no = sock.send(data)
|
sent_no = sock.send(data)
|
||||||
data = data[sent_no:]
|
data = data[sent_no:]
|
||||||
|
|
||||||
async def close_sock(self, sock: socket.socket):
|
async def close_sock(self, sock: socket.socket):
|
||||||
"""
|
"""
|
||||||
Closes the socket asynchronously
|
Closes the socket asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await want_write(sock)
|
await want_write(sock)
|
||||||
self.selector.unregister(sock)
|
self.selector.unregister(sock)
|
||||||
return sock.close()
|
return sock.close()
|
||||||
|
|
||||||
async def connect_sock(self, sock: socket.socket, addr: tuple):
|
async def connect_sock(self, sock: socket.socket, addr: tuple):
|
||||||
"""
|
"""
|
||||||
Connects a socket asynchronously
|
Connects a socket asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try: # "Borrowed" from curio
|
try: # "Borrowed" from curio
|
||||||
return sock.connect(addr)
|
return sock.connect(addr)
|
||||||
except WantWrite:
|
except WantWrite:
|
||||||
await want_write(sock)
|
await want_write(sock)
|
||||||
err = sock.getsockopt(SOL_SOCKET, SO_ERROR)
|
err = sock.getsockopt(SOL_SOCKET, SO_ERROR)
|
||||||
if err != 0:
|
if err != 0:
|
||||||
raise OSError(err, f"Connect call failed: {addr}")
|
raise OSError(err, f"Connect call failed: {addr}")
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
"""
|
"""
|
||||||
Exceptions for giambio
|
Exceptions for giambio
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class GiambioError(Exception):
|
class GiambioError(Exception):
|
||||||
"""
|
"""
|
||||||
Base class for giambio exceptions
|
Base class for giambio exceptions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class InternalError(GiambioError):
|
class InternalError(GiambioError):
|
||||||
"""
|
"""
|
||||||
Internal exception
|
Internal exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class CancelledError(BaseException):
|
class CancelledError(BaseException):
|
||||||
"""
|
"""
|
||||||
Exception raised by the giambio.objects.Task.cancel() method
|
Exception raised by the giambio.objects.Task.cancel() method
|
||||||
to terminate a child task. This should NOT be catched, or
|
to terminate a child task. This should NOT be catched, or
|
||||||
at least it should be re-raised and never ignored
|
at least it should be re-raised and never ignored
|
||||||
"""
|
"""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class ResourceBusy(GiambioError):
|
class ResourceBusy(GiambioError):
|
||||||
"""
|
"""
|
||||||
Exception that is raised when a resource is accessed by more than
|
Exception that is raised when a resource is accessed by more than
|
||||||
one task at a time
|
one task at a time
|
||||||
"""
|
"""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class ResourceClosed(GiambioError):
|
class ResourceClosed(GiambioError):
|
||||||
"""
|
"""
|
||||||
Raised when I/O is attempted on a closed resource
|
Raised when I/O is attempted on a closed resource
|
||||||
"""
|
"""
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
|
@ -1,158 +1,158 @@
|
||||||
"""
|
"""
|
||||||
Various object wrappers and abstraction layers
|
Various object wrappers and abstraction layers
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import types
|
import types
|
||||||
from .traps import join, cancel, event_set, event_wait
|
from .traps import join, cancel, event_set, event_wait
|
||||||
from heapq import heappop, heappush
|
from heapq import heappop, heappush
|
||||||
from .exceptions import GiambioError
|
from .exceptions import GiambioError
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Task:
|
class Task:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A simple wrapper around a coroutine object
|
A simple wrapper around a coroutine object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
coroutine: types.CoroutineType
|
coroutine: types.CoroutineType
|
||||||
name: str
|
name: str
|
||||||
cancelled: bool = False # True if the task gets cancelled
|
cancelled: bool = False # True if the task gets cancelled
|
||||||
exc: BaseException = None
|
exc: BaseException = None
|
||||||
result: object = None
|
result: object = None
|
||||||
finished: bool = False
|
finished: bool = False
|
||||||
status: str = "init"
|
status: str = "init"
|
||||||
steps: int = 0
|
steps: int = 0
|
||||||
last_io: tuple = ()
|
last_io: tuple = ()
|
||||||
parent: object = None
|
parent: object = None
|
||||||
joined: bool= False
|
joined: bool= False
|
||||||
cancel_pending: bool = False
|
cancel_pending: bool = False
|
||||||
waiters: list = field(default_factory=list)
|
waiters: list = field(default_factory=list)
|
||||||
|
|
||||||
def run(self, what=None):
|
def run(self, what=None):
|
||||||
"""
|
"""
|
||||||
Simple abstraction layer over coroutines' ``send`` method
|
Simple abstraction layer over coroutines' ``send`` method
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.coroutine.send(what)
|
return self.coroutine.send(what)
|
||||||
|
|
||||||
def throw(self, err: Exception):
|
def throw(self, err: Exception):
|
||||||
"""
|
"""
|
||||||
Simple abstraction layer over coroutines ``throw`` method
|
Simple abstraction layer over coroutines ``throw`` method
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.coroutine.throw(err)
|
return self.coroutine.throw(err)
|
||||||
|
|
||||||
async def join(self):
|
async def join(self):
|
||||||
"""
|
"""
|
||||||
Joins the task
|
Joins the task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res = await join(self)
|
res = await join(self)
|
||||||
if self.exc:
|
if self.exc:
|
||||||
raise self.exc
|
raise self.exc
|
||||||
return res
|
return res
|
||||||
|
|
||||||
async def cancel(self):
|
async def cancel(self):
|
||||||
"""
|
"""
|
||||||
Cancels the task
|
Cancels the task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await cancel(self)
|
await cancel(self)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.coroutine.close()
|
self.coroutine.close()
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
"""
|
"""
|
||||||
A class designed similarly to threading.Event
|
A class designed similarly to threading.Event
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Object constructor
|
Object constructor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.set = False
|
self.set = False
|
||||||
self.waiters = []
|
self.waiters = []
|
||||||
self.event_caught = False
|
self.event_caught = False
|
||||||
|
|
||||||
async def trigger(self):
|
async def trigger(self):
|
||||||
"""
|
"""
|
||||||
Sets the event, waking up all tasks that called
|
Sets the event, waking up all tasks that called
|
||||||
pause() on us
|
pause() on us
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.set:
|
if self.set:
|
||||||
raise GiambioError("The event has already been set")
|
raise GiambioError("The event has already been set")
|
||||||
await event_set(self)
|
await event_set(self)
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
"""
|
"""
|
||||||
Waits until the event is set
|
Waits until the event is set
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await event_wait(self)
|
await event_wait(self)
|
||||||
|
|
||||||
|
|
||||||
class TimeQueue:
|
class TimeQueue:
|
||||||
"""
|
"""
|
||||||
An abstraction layer over a heap queue based on time. This is where
|
An abstraction layer over a heap queue based on time. This is where
|
||||||
sleeping tasks will be put when they are not running
|
sleeping tasks will be put when they are not running
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, clock):
|
def __init__(self, clock):
|
||||||
"""
|
"""
|
||||||
Object constructor
|
Object constructor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.sequence = 0
|
self.sequence = 0
|
||||||
self.container = []
|
self.container = []
|
||||||
|
|
||||||
def __contains__(self, item):
|
def __contains__(self, item):
|
||||||
return item in self.container
|
return item in self.container
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.container)
|
return iter(self.container)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return self.container.__getitem__(item)
|
return self.container.__getitem__(item)
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return bool(self.container)
|
return bool(self.container)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"TimeQueue({self.container}, clock={self.clock})"
|
return f"TimeQueue({self.container}, clock={self.clock})"
|
||||||
|
|
||||||
def put(self, item, amount):
|
def put(self, item, amount):
|
||||||
"""
|
"""
|
||||||
Pushes an item onto the queue with its unique
|
Pushes an item onto the queue with its unique
|
||||||
time amount and ID
|
time amount and ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
heappush(self.container, (self.clock() + amount, self.sequence, item))
|
heappush(self.container, (self.clock() + amount, self.sequence, item))
|
||||||
self.sequence += 1
|
self.sequence += 1
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
Gets the first task that is meant to run
|
Gets the first task that is meant to run
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return heappop(self.container)[2]
|
return heappop(self.container)[2]
|
||||||
|
|
198
giambio/run.py
198
giambio/run.py
|
@ -1,99 +1,99 @@
|
||||||
"""
|
"""
|
||||||
Helper methods and public API
|
Helper methods and public API
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
from .core import AsyncScheduler
|
from .core import AsyncScheduler
|
||||||
from .exceptions import GiambioError
|
from .exceptions import GiambioError
|
||||||
from .context import TaskManager
|
from .context import TaskManager
|
||||||
from .socket import AsyncSocket
|
from .socket import AsyncSocket
|
||||||
from types import FunctionType, CoroutineType, GeneratorType
|
from types import FunctionType, CoroutineType, GeneratorType
|
||||||
|
|
||||||
|
|
||||||
thread_local = threading.local()
|
thread_local = threading.local()
|
||||||
|
|
||||||
|
|
||||||
def get_event_loop():
|
def get_event_loop():
|
||||||
"""
|
"""
|
||||||
Returns the event loop associated to the current
|
Returns the event loop associated to the current
|
||||||
thread
|
thread
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return thread_local.loop
|
return thread_local.loop
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise GiambioError("no event loop set") from None
|
raise GiambioError("no event loop set") from None
|
||||||
|
|
||||||
|
|
||||||
def new_event_loop():
|
def new_event_loop():
|
||||||
"""
|
"""
|
||||||
Associates a new event loop to the current thread
|
Associates a new event loop to the current thread
|
||||||
and deactivates the old one. This should not be
|
and deactivates the old one. This should not be
|
||||||
called explicitly unless you know what you're doing
|
called explicitly unless you know what you're doing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop = thread_local.loop
|
loop = thread_local.loop
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
thread_local.loop = AsyncScheduler()
|
thread_local.loop = AsyncScheduler()
|
||||||
else:
|
else:
|
||||||
if not loop.done():
|
if not loop.done():
|
||||||
raise GiambioError("cannot set event loop while running")
|
raise GiambioError("cannot set event loop while running")
|
||||||
else:
|
else:
|
||||||
thread_local.loop = AsyncScheduler()
|
thread_local.loop = AsyncScheduler()
|
||||||
|
|
||||||
|
|
||||||
def run(func: FunctionType, *args):
|
def run(func: FunctionType, *args):
|
||||||
"""
|
"""
|
||||||
Starts the event loop from a synchronous entry point
|
Starts the event loop from a synchronous entry point
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(func, (CoroutineType, GeneratorType)):
|
if isinstance(func, (CoroutineType, GeneratorType)):
|
||||||
raise RuntimeError("Looks like you tried to call giambio.run(your_func(arg1, arg2, ...)), that is wrong!"
|
raise RuntimeError("Looks like you tried to call giambio.run(your_func(arg1, arg2, ...)), that is wrong!"
|
||||||
"\nWhat you wanna do, instead, is this: giambio.run(your_func, arg1, arg2, ...)")
|
"\nWhat you wanna do, instead, is this: giambio.run(your_func, arg1, arg2, ...)")
|
||||||
new_event_loop()
|
new_event_loop()
|
||||||
thread_local.loop.start(func, *args)
|
thread_local.loop.start(func, *args)
|
||||||
|
|
||||||
|
|
||||||
def clock():
|
def clock():
|
||||||
"""
|
"""
|
||||||
Returns the current clock time of the thread-local event
|
Returns the current clock time of the thread-local event
|
||||||
loop
|
loop
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return thread_local.loop.clock()
|
return thread_local.loop.clock()
|
||||||
|
|
||||||
|
|
||||||
def wrap_socket(sock: socket.socket) -> AsyncSocket:
|
def wrap_socket(sock: socket.socket) -> AsyncSocket:
|
||||||
"""
|
"""
|
||||||
Wraps a synchronous socket into a giambio.socket.AsyncSocket
|
Wraps a synchronous socket into a giambio.socket.AsyncSocket
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return thread_local.loop.wrap_socket(sock)
|
return thread_local.loop.wrap_socket(sock)
|
||||||
|
|
||||||
|
|
||||||
def create_pool():
|
def create_pool():
|
||||||
"""
|
"""
|
||||||
Creates an async pool
|
Creates an async pool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return TaskManager(thread_local.loop)
|
return TaskManager(thread_local.loop)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise RuntimeError("It appears that giambio is not running, did you call giambio.async_pool()"
|
raise RuntimeError("It appears that giambio is not running, did you call giambio.async_pool()"
|
||||||
" outside of an async context?") from None
|
" outside of an async context?") from None
|
||||||
|
|
|
@ -1,100 +1,100 @@
|
||||||
"""
|
"""
|
||||||
Basic abstraction layer for giambio asynchronous sockets
|
Basic abstraction layer for giambio asynchronous sockets
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
from .exceptions import ResourceClosed
|
from .exceptions import ResourceClosed
|
||||||
from .traps import sleep
|
from .traps import sleep
|
||||||
|
|
||||||
|
|
||||||
# Stolen from curio
|
# Stolen from curio
|
||||||
try:
|
try:
|
||||||
from ssl import SSLWantReadError, SSLWantWriteError
|
from ssl import SSLWantReadError, SSLWantWriteError
|
||||||
WantRead = (BlockingIOError, InterruptedError, SSLWantReadError)
|
WantRead = (BlockingIOError, InterruptedError, SSLWantReadError)
|
||||||
WantWrite = (BlockingIOError, InterruptedError, SSLWantWriteError)
|
WantWrite = (BlockingIOError, InterruptedError, SSLWantWriteError)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
WantRead = (BlockingIOError, InterruptedError)
|
WantRead = (BlockingIOError, InterruptedError)
|
||||||
WantWrite = (BlockingIOError, InterruptedError)
|
WantWrite = (BlockingIOError, InterruptedError)
|
||||||
|
|
||||||
|
|
||||||
class AsyncSocket(object):
|
class AsyncSocket(object):
|
||||||
"""
|
"""
|
||||||
Abstraction layer for asynchronous TCP sockets
|
Abstraction layer for asynchronous TCP sockets
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sock: socket.socket, loop):
|
def __init__(self, sock: socket.socket, loop):
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self.sock.setblocking(False)
|
self.sock.setblocking(False)
|
||||||
|
|
||||||
async def receive(self, max_size: int):
|
async def receive(self, max_size: int):
|
||||||
"""
|
"""
|
||||||
Receives up to max_size bytes from a socket asynchronously
|
Receives up to max_size bytes from a socket asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise ResourceClosed("I/O operation on closed socket")
|
raise ResourceClosed("I/O operation on closed socket")
|
||||||
return await self.loop.read_sock(self.sock, max_size)
|
return await self.loop.read_sock(self.sock, max_size)
|
||||||
|
|
||||||
async def accept(self):
|
async def accept(self):
|
||||||
"""
|
"""
|
||||||
Accepts the socket, completing the 3-step TCP handshake asynchronously
|
Accepts the socket, completing the 3-step TCP handshake asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise ResourceClosed("I/O operation on closed socket")
|
raise ResourceClosed("I/O operation on closed socket")
|
||||||
to_wrap = await self.loop.accept_sock(self.sock)
|
to_wrap = await self.loop.accept_sock(self.sock)
|
||||||
return self.loop.wrap_socket(to_wrap[0]), to_wrap[1]
|
return self.loop.wrap_socket(to_wrap[0]), to_wrap[1]
|
||||||
|
|
||||||
async def send_all(self, data: bytes):
|
async def send_all(self, data: bytes):
|
||||||
"""
|
"""
|
||||||
Sends all data inside the buffer asynchronously until it is empty
|
Sends all data inside the buffer asynchronously until it is empty
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise ResourceClosed("I/O operation on closed socket")
|
raise ResourceClosed("I/O operation on closed socket")
|
||||||
return await self.loop.sock_sendall(self.sock, data)
|
return await self.loop.sock_sendall(self.sock, data)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
"""
|
"""
|
||||||
Closes the socket asynchronously
|
Closes the socket asynchronously
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise ResourceClosed("I/O operation on closed socket")
|
raise ResourceClosed("I/O operation on closed socket")
|
||||||
await self.loop.close_sock(self.sock)
|
await self.loop.close_sock(self.sock)
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
|
||||||
async def connect(self, addr: tuple):
|
async def connect(self, addr: tuple):
|
||||||
"""
|
"""
|
||||||
Connects the socket to an endpoint
|
Connects the socket to an endpoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._closed:
|
if self._closed:
|
||||||
raise ResourceClosed("I/O operation on closed socket")
|
raise ResourceClosed("I/O operation on closed socket")
|
||||||
await self.loop.connect_sock(self.sock, addr)
|
await self.loop.connect_sock(self.sock, addr)
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, *_):
|
async def __aexit__(self, *_):
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"giambio.socket.AsyncSocket({self.sock}, {self.loop})"
|
return f"giambio.socket.AsyncSocket({self.sock}, {self.loop})"
|
||||||
|
|
268
giambio/traps.py
268
giambio/traps.py
|
@ -1,134 +1,134 @@
|
||||||
"""
|
"""
|
||||||
Implementation for all giambio traps, which are hooks
|
Implementation for all giambio traps, which are hooks
|
||||||
into the event loop and allow it to switch tasks.
|
into the event loop and allow it to switch tasks.
|
||||||
These coroutines are the one and only way to interact
|
These coroutines are the one and only way to interact
|
||||||
with the event loop from the user's perspective, and
|
with the event loop from the user's perspective, and
|
||||||
the entire library is based on them
|
the entire library is based on them
|
||||||
|
|
||||||
Copyright (C) 2020 nocturn9x
|
Copyright (C) 2020 nocturn9x
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
|
||||||
@types.coroutine
|
@types.coroutine
|
||||||
def create_trap(method, *args):
|
def create_trap(method, *args):
|
||||||
"""
|
"""
|
||||||
Creates and yields a trap. This
|
Creates and yields a trap. This
|
||||||
is the lowest-level method to
|
is the lowest-level method to
|
||||||
interact with the event loop
|
interact with the event loop
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = yield method, *args
|
data = yield method, *args
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def sleep(seconds: int):
|
async def sleep(seconds: int):
|
||||||
"""
|
"""
|
||||||
Pause the execution of an async function for a given amount of seconds,
|
Pause the execution of an async function for a given amount of seconds,
|
||||||
without blocking the entire event loop, which keeps watching for other events
|
without blocking the entire event loop, which keeps watching for other events
|
||||||
|
|
||||||
This function is also useful as a sort of checkpoint, because it returns
|
This function is also useful as a sort of checkpoint, because it returns
|
||||||
control to the scheduler, which can then switch to another task. If your code
|
control to the scheduler, which can then switch to another task. If your code
|
||||||
doesn't have enough calls to async functions (or 'checkpoints') this might
|
doesn't have enough calls to async functions (or 'checkpoints') this might
|
||||||
prevent the scheduler from switching tasks properly. If you feel like this
|
prevent the scheduler from switching tasks properly. If you feel like this
|
||||||
happens in your code, try adding a call to giambio.sleep(0) somewhere.
|
happens in your code, try adding a call to giambio.sleep(0) somewhere.
|
||||||
This will act as a checkpoint without actually pausing the execution
|
This will act as a checkpoint without actually pausing the execution
|
||||||
of your function, but it will allow the scheduler to switch tasks
|
of your function, but it will allow the scheduler to switch tasks
|
||||||
|
|
||||||
:param seconds: The amount of seconds to sleep for
|
:param seconds: The amount of seconds to sleep for
|
||||||
:type seconds: int
|
:type seconds: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert seconds >= 0, "The time delay can't be negative"
|
assert seconds >= 0, "The time delay can't be negative"
|
||||||
await create_trap("sleep", seconds)
|
await create_trap("sleep", seconds)
|
||||||
|
|
||||||
|
|
||||||
async def current_task():
|
async def current_task():
|
||||||
"""
|
"""
|
||||||
Gets the currently running task
|
Gets the currently running task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await create_trap("get_running")
|
return await create_trap("get_running")
|
||||||
|
|
||||||
|
|
||||||
async def join(task):
|
async def join(task):
|
||||||
"""
|
"""
|
||||||
Awaits a given task for completion
|
Awaits a given task for completion
|
||||||
|
|
||||||
:param task: The task to join
|
:param task: The task to join
|
||||||
:type task: class: Task
|
:type task: class: Task
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await create_trap("join")
|
return await create_trap("join")
|
||||||
|
|
||||||
|
|
||||||
async def cancel(task):
|
async def cancel(task):
|
||||||
"""
|
"""
|
||||||
Cancels the given task
|
Cancels the given task
|
||||||
|
|
||||||
The concept of cancellation is tricky, because there is no real way to 'stop'
|
The concept of cancellation is tricky, because there is no real way to 'stop'
|
||||||
a task if not by raising an exception inside it and ignoring whatever it
|
a task if not by raising an exception inside it and ignoring whatever it
|
||||||
returns (and also hoping that the task won't cause collateral damage). It
|
returns (and also hoping that the task won't cause collateral damage). It
|
||||||
is highly recommended that when you write async code you take into account
|
is highly recommended that when you write async code you take into account
|
||||||
that it might be cancelled at any time. You might think to just ignore the
|
that it might be cancelled at any time. You might think to just ignore the
|
||||||
cancellation exception and be done with it, but doing so *will* break your
|
cancellation exception and be done with it, but doing so *will* break your
|
||||||
code, so if you really wanna do that be sure to re-raise it when done!
|
code, so if you really wanna do that be sure to re-raise it when done!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await create_trap("cancel")
|
await create_trap("cancel")
|
||||||
assert task.cancelled, f"Coroutine ignored CancelledError"
|
assert task.cancelled, f"Coroutine ignored CancelledError"
|
||||||
|
|
||||||
|
|
||||||
async def want_read(stream):
|
async def want_read(stream):
|
||||||
"""
|
"""
|
||||||
Notifies the event loop that a task wants to read from the given
|
Notifies the event loop that a task wants to read from the given
|
||||||
resource
|
resource
|
||||||
|
|
||||||
:param stream: The resource that needs to be read
|
:param stream: The resource that needs to be read
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await create_trap("want_read", stream)
|
await create_trap("want_read", stream)
|
||||||
|
|
||||||
|
|
||||||
async def want_write(stream):
|
async def want_write(stream):
|
||||||
"""
|
"""
|
||||||
Notifies the event loop that a task wants to write on the given
|
Notifies the event loop that a task wants to write on the given
|
||||||
resource
|
resource
|
||||||
|
|
||||||
:param stream: The resource that needs to be written
|
:param stream: The resource that needs to be written
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await create_trap("want_write", stream)
|
await create_trap("want_write", stream)
|
||||||
|
|
||||||
|
|
||||||
async def event_set(event):
|
async def event_set(event):
|
||||||
"""
|
"""
|
||||||
Communicates to the loop that the given event object
|
Communicates to the loop that the given event object
|
||||||
must be set. This is important as the loop constantly
|
must be set. This is important as the loop constantly
|
||||||
checks for active events to deliver them
|
checks for active events to deliver them
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await create_trap("event_set", event)
|
await create_trap("event_set", event)
|
||||||
|
|
||||||
|
|
||||||
async def event_wait(event):
|
async def event_wait(event):
|
||||||
"""
|
"""
|
||||||
Notifies the event loop that the current task has to wait
|
Notifies the event loop that the current task has to wait
|
||||||
for the given event to trigger
|
for the given event to trigger
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await create_trap("event_wait", event)
|
await create_trap("event_wait", event)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
home = /usr
|
||||||
|
implementation = CPython
|
||||||
|
version_info = 3.7.3.final.0
|
||||||
|
virtualenv = 20.1.0
|
||||||
|
include-system-site-packages = false
|
||||||
|
base-prefix = /usr
|
||||||
|
base-exec-prefix = /usr
|
||||||
|
base-executable = /usr/bin/python3
|
44
setup.py
44
setup.py
|
@ -1,22 +1,22 @@
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
with open("README.md", "r") as readme:
|
with open("README.md", "r") as readme:
|
||||||
long_description = readme.read()
|
long_description = readme.read()
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="GiambIO",
|
name="GiambIO",
|
||||||
version="1.0",
|
version="1.0",
|
||||||
author="Nocturn9x aka IsGiambyy",
|
author="Nocturn9x aka IsGiambyy",
|
||||||
author_email="hackhab@gmail.com",
|
author_email="hackhab@gmail.com",
|
||||||
description="Asynchronous Python made easy (and friendly)",
|
description="Asynchronous Python made easy (and friendly)",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://github.com/nocturn9x/giambio",
|
url="https://github.com/nocturn9x/giambio",
|
||||||
packages=setuptools.find_packages(),
|
packages=setuptools.find_packages(),
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"License :: OSI Approved :: Apache License 2.0",
|
"License :: OSI Approved :: Apache License 2.0",
|
||||||
],
|
],
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.6",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
|
|
||||||
# A test for context managers
|
# A test for context managers
|
||||||
|
|
||||||
|
|
||||||
async def countdown(n: int):
|
async def countdown(n: int):
|
||||||
print(f"Counting down from {n}!")
|
print(f"Counting down from {n}!")
|
||||||
while n > 0:
|
while n > 0:
|
||||||
print(f"Down {n}")
|
print(f"Down {n}")
|
||||||
n -= 1
|
n -= 1
|
||||||
await giambio.sleep(1)
|
await giambio.sleep(1)
|
||||||
# raise Exception("oh no man") # Uncomment to test propagation
|
# raise Exception("oh no man") # Uncomment to test propagation
|
||||||
print("Countdown over")
|
print("Countdown over")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
async def countup(stop: int, step: int = 1):
|
async def countup(stop: int, step: int = 1):
|
||||||
print(f"Counting up to {stop}!")
|
print(f"Counting up to {stop}!")
|
||||||
x = 0
|
x = 0
|
||||||
while x < stop:
|
while x < stop:
|
||||||
print(f"Up {x}")
|
print(f"Up {x}")
|
||||||
x += 1
|
x += 1
|
||||||
await giambio.sleep(step)
|
await giambio.sleep(step)
|
||||||
print("Countup over")
|
print("Countup over")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
start = giambio.clock()
|
start = giambio.clock()
|
||||||
try:
|
try:
|
||||||
async with giambio.create_pool() as pool:
|
async with giambio.create_pool() as pool:
|
||||||
pool.spawn(countdown, 10)
|
pool.spawn(countdown, 10)
|
||||||
pool.spawn(countup, 5, 2)
|
pool.spawn(countup, 5, 2)
|
||||||
print("Children spawned, awaiting completion")
|
print("Children spawned, awaiting completion")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Got -> {type(e).__name__}: {e}")
|
print(f"Got -> {type(e).__name__}: {e}")
|
||||||
print(f"Task execution complete in {giambio.clock() - start:.2f} seconds")
|
print(f"Task execution complete in {giambio.clock() - start:.2f} seconds")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
giambio.run(main)
|
giambio.run(main)
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
import giambio
|
import giambio
|
||||||
|
|
||||||
|
|
||||||
# A test for events
|
# A test for events
|
||||||
|
|
||||||
|
|
||||||
async def child(ev: giambio.Event, pause: int):
|
async def child(ev: giambio.Event, pause: int):
|
||||||
print("[child] Child is alive! Going to wait until notified")
|
print("[child] Child is alive! Going to wait until notified")
|
||||||
start_total = giambio.clock()
|
start_total = giambio.clock()
|
||||||
await ev.wait()
|
await ev.wait()
|
||||||
end_pause = giambio.clock() - start_total
|
end_pause = giambio.clock() - start_total
|
||||||
print(f"[child] Parent set the event, exiting in {pause} seconds")
|
print(f"[child] Parent set the event, exiting in {pause} seconds")
|
||||||
start_sleep = giambio.clock()
|
start_sleep = giambio.clock()
|
||||||
await giambio.sleep(pause)
|
await giambio.sleep(pause)
|
||||||
end_sleep = giambio.clock() - start_sleep
|
end_sleep = giambio.clock() - start_sleep
|
||||||
end_total = giambio.clock() - start_total
|
end_total = giambio.clock() - start_total
|
||||||
print(f"[child] Done! Slept for {end_total} seconds total ({end_pause} paused, {end_sleep} sleeping), nice nap!")
|
print(f"[child] Done! Slept for {end_total} seconds total ({end_pause} paused, {end_sleep} sleeping), nice nap!")
|
||||||
|
|
||||||
|
|
||||||
async def parent(pause: int = 1):
|
async def parent(pause: int = 1):
|
||||||
async with giambio.create_pool() as pool:
|
async with giambio.create_pool() as pool:
|
||||||
event = giambio.Event()
|
event = giambio.Event()
|
||||||
print("[parent] Spawning child task")
|
print("[parent] Spawning child task")
|
||||||
pool.spawn(child, event, pause + 2)
|
pool.spawn(child, event, pause + 2)
|
||||||
start = giambio.clock()
|
start = giambio.clock()
|
||||||
print(f"[parent] Sleeping {pause} second(s) before setting the event")
|
print(f"[parent] Sleeping {pause} second(s) before setting the event")
|
||||||
await giambio.sleep(pause)
|
await giambio.sleep(pause)
|
||||||
await event.trigger()
|
await event.trigger()
|
||||||
print("[parent] Event set, awaiting child")
|
print("[parent] Event set, awaiting child")
|
||||||
end = giambio.clock() - start
|
end = giambio.clock() - start
|
||||||
print(f"[parent] Child exited in {end} seconds")
|
print(f"[parent] Child exited in {end} seconds")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
giambio.run(parent, 3)
|
giambio.run(parent, 3)
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import giambio
|
|
||||||
|
|
||||||
|
|
||||||
async def child(sleep: int, ident: int):
|
|
||||||
start = giambio.clock() # This returns the current time from giambio's perspective
|
|
||||||
print(f"[child {ident}] Gonna sleep for {sleep} seconds!")
|
|
||||||
await giambio.sleep(sleep)
|
|
||||||
end = giambio.clock() - start
|
|
||||||
print(f"[child {ident}] I woke up! Slept for {end} seconds")
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
print("[parent] Spawning children")
|
|
||||||
task = giambio.spawn(child, 1, 1) # We spawn a child task
|
|
||||||
task2 = giambio.spawn(child, 2, 2) # and why not? another one!
|
|
||||||
start = giambio.clock()
|
|
||||||
print("[parent] Children spawned, awaiting completion")
|
|
||||||
await task.join()
|
|
||||||
await task2.join()
|
|
||||||
end = giambio.clock() - start
|
|
||||||
print(f"[parent] Execution terminated in {end} seconds")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
giambio.run(main) # Start the async context
|
|
109
tests/server.py
109
tests/server.py
|
@ -1,54 +1,55 @@
|
||||||
import giambio
|
import giambio
|
||||||
from giambio.socket import AsyncSocket
|
from giambio.socket import AsyncSocket
|
||||||
import socket
|
import socket
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
# A test to check for asynchronous I/O
|
# A test to check for asynchronous I/O
|
||||||
|
|
||||||
|
|
||||||
async def serve(address: tuple):
|
async def serve(address: tuple):
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.bind(address)
|
sock.bind(address)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
sock.listen(5)
|
sock.listen(5)
|
||||||
asock = giambio.wrap_socket(sock) # We make the socket an async socket
|
asock = giambio.wrap_socket(sock) # We make the socket an async socket
|
||||||
logging.info(f"Serving asynchronously at {address[0]}:{address[1]}")
|
logging.info(f"Serving asynchronously at {address[0]}:{address[1]}")
|
||||||
async with giambio.create_pool() as pool:
|
async with giambio.create_pool() as pool:
|
||||||
conn, addr = await asock.accept()
|
while True:
|
||||||
logging.info(f"{addr[0]}:{addr[1]} connected")
|
conn, addr = await asock.accept()
|
||||||
pool.spawn(handler, conn, addr)
|
logging.info(f"{addr[0]}:{addr[1]} connected")
|
||||||
|
pool.spawn(handler, conn, addr)
|
||||||
|
print("oof done")
|
||||||
async def handler(sock: AsyncSocket, addr: tuple):
|
|
||||||
addr = f"{addr[0]}:{addr[1]}"
|
async def handler(sock: AsyncSocket, addr: tuple):
|
||||||
async with sock:
|
addr = f"{addr[0]}:{addr[1]}"
|
||||||
await sock.send_all(b"Welcome to the server pal, feel free to send me something!\n")
|
async with sock:
|
||||||
while True:
|
await sock.send_all(b"Welcome to the server pal, feel free to send me something!\n")
|
||||||
await sock.send_all(b"-> ")
|
while True:
|
||||||
data = await sock.receive(1024)
|
await sock.send_all(b"-> ")
|
||||||
if not data:
|
data = await sock.receive(1024)
|
||||||
break
|
if not data:
|
||||||
elif data == b"raise\n":
|
break
|
||||||
await sock.send_all(b"I'm dead dude\n")
|
elif data == b"raise\n":
|
||||||
raise TypeError("Oh, no, I'm gonna die!")
|
await sock.send_all(b"I'm dead dude\n")
|
||||||
to_send_back = data
|
raise TypeError("Oh, no, I'm gonna die!")
|
||||||
data = data.decode("utf-8").encode("unicode_escape")
|
to_send_back = data
|
||||||
logging.info(f"Got: '{data.decode('utf-8')}' from {addr}")
|
data = data.decode("utf-8").encode("unicode_escape")
|
||||||
await sock.send_all(b"Got: " + to_send_back)
|
logging.info(f"Got: '{data.decode('utf-8')}' from {addr}")
|
||||||
logging.info(f"Echoed back '{data.decode('utf-8')}' to {addr}")
|
await sock.send_all(b"Got: " + to_send_back)
|
||||||
logging.info(f"Connection from {addr} closed")
|
logging.info(f"Echoed back '{data.decode('utf-8')}' to {addr}")
|
||||||
|
logging.info(f"Connection from {addr} closed")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 1500
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(level=20, format="[%(levelname)s] %(asctime)s %(message)s", datefmt="%d/%m/%Y %p")
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 1500
|
||||||
try:
|
logging.basicConfig(level=20, format="[%(levelname)s] %(asctime)s %(message)s", datefmt="%d/%m/%Y %p")
|
||||||
giambio.run(serve, ("localhost", port))
|
try:
|
||||||
except (Exception, KeyboardInterrupt) as error: # Exceptions propagate!
|
giambio.run(serve, ("localhost", port))
|
||||||
if isinstance(error, KeyboardInterrupt):
|
except (Exception, KeyboardInterrupt) as error: # Exceptions propagate!
|
||||||
logging.info("Ctrl+C detected, exiting")
|
if isinstance(error, KeyboardInterrupt):
|
||||||
else:
|
logging.info("Ctrl+C detected, exiting")
|
||||||
logging.error(f"Exiting due to a {type(error).__name__}: {error}")
|
else:
|
||||||
|
logging.error(f"Exiting due to a {type(error).__name__}: {error}")
|
||||||
|
|
Loading…
Reference in New Issue