Import python venv for stability

This commit is contained in:
2026-02-15 21:24:16 -08:00
parent 1343e93a59
commit 7d784705c9
4997 changed files with 1628270 additions and 0 deletions
@@ -0,0 +1,590 @@
Metadata-Version: 2.4
Name: multitasking
Version: 0.0.12
Summary: Non-blocking Python methods using decorators
Home-page: https://github.com/ranaroussi/multitasking
Author: Ran Aroussi
Author-email: ran@aroussi.com
License: Apache
Keywords: multitasking multitask threading async
Platform: any
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Development Status :: 5 - Production/Stable
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: platform
Dynamic: summary
MultiTasking: Non-blocking Python methods using decorators
==========================================================
|Python version| |PyPi version| |PyPi status| |PyPi downloads|
|CodeFactor| |Star this repo| |Follow me on twitter|
--------------
**MultiTasking** is a lightweight Python library that lets you convert
your Python methods into asynchronous, non-blocking methods simply by
using a decorator. Perfect for I/O-bound tasks, API calls, web scraping,
and any scenario where you want to run multiple operations concurrently
without the complexity of manual thread or process management.
✨ **What's New in v0.0.12**
----------------------------
- 🎯 **Full Type Hint Support**: Complete type annotations for better
IDE support and code safety
- 📚 **Enhanced Documentation**: Comprehensive docstrings and inline
comments for better maintainability
- 🔧 **Improved Error Handling**: More robust exception handling with
specific error types
- 🚀 **Better Performance**: Optimized task creation and management
logic
- 🛡️ **Code Quality**: PEP8 compliant, linter-friendly codebase
Quick Start
-----------
.. code:: python
import multitasking
import time
@multitasking.task
def fetch_data(url_id):
# Simulate API call or I/O operation
time.sleep(1)
return f"Data from {url_id}"
# These run concurrently, not sequentially!
for i in range(5):
fetch_data(i)
# Wait for all tasks to complete
multitasking.wait_for_tasks()
print("All data fetched!")
Basic Example
-------------
.. code:: python
# example.py
import multitasking
import time
import random
import signal
# Kill all tasks on ctrl-c (recommended for development)
signal.signal(signal.SIGINT, multitasking.killall)
# Or, wait for tasks to finish gracefully on ctrl-c:
# signal.signal(signal.SIGINT, multitasking.wait_for_tasks)
@multitasking.task # <== this is all it takes! 🎉
def hello(count):
sleep_time = random.randint(1, 10) / 2
print(f"Hello {count} (sleeping for {sleep_time}s)")
time.sleep(sleep_time)
print(f"Goodbye {count} (slept for {sleep_time}s)")
if __name__ == "__main__":
# Launch 10 concurrent tasks
for i in range(10):
hello(i + 1)
# Wait for all tasks to complete
multitasking.wait_for_tasks()
print("All tasks completed!")
**Output:**
.. code:: bash
$ python example.py
Hello 1 (sleeping for 0.5s)
Hello 2 (sleeping for 1.0s)
Hello 3 (sleeping for 5.0s)
Hello 4 (sleeping for 0.5s)
Hello 5 (sleeping for 2.5s)
Hello 6 (sleeping for 3.0s)
Hello 7 (sleeping for 0.5s)
Hello 8 (sleeping for 4.0s)
Hello 9 (sleeping for 3.0s)
Hello 10 (sleeping for 1.0s)
Goodbye 1 (slept for 0.5s)
Goodbye 4 (slept for 0.5s)
Goodbye 7 (slept for 0.5s)
Goodbye 2 (slept for 1.0s)
Goodbye 10 (slept for 1.0s)
Goodbye 5 (slept for 2.5s)
Goodbye 6 (slept for 3.0s)
Goodbye 9 (slept for 3.0s)
Goodbye 8 (slept for 4.0s)
Goodbye 3 (slept for 5.0s)
All tasks completed!
Advanced Usage
==============
Real-World Examples
-------------------
**Web Scraping with Concurrent Requests:**
.. code:: python
import multitasking
import requests
import signal
signal.signal(signal.SIGINT, multitasking.killall)
@multitasking.task
def fetch_url(url):
try:
response = requests.get(url, timeout=10)
print(f"✅ {url}: {response.status_code}")
return response.text
except Exception as e:
print(f"❌ {url}: {str(e)}")
return None
# Fetch multiple URLs concurrently
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/status/200",
"https://httpbin.org/json"
]
for url in urls:
fetch_url(url)
multitasking.wait_for_tasks()
print(f"Processed {len(urls)} URLs concurrently!")
**Database Operations:**
.. code:: python
import multitasking
import sqlite3
import time
@multitasking.task
def process_batch(batch_id, data_batch):
# Simulate database processing
conn = sqlite3.connect(f'batch_{batch_id}.db')
# ... database operations ...
conn.close()
print(f"Processed batch {batch_id} with {len(data_batch)} records")
# Process multiple data batches concurrently
large_dataset = list(range(1000))
batch_size = 100
for i in range(0, len(large_dataset), batch_size):
batch = large_dataset[i:i + batch_size]
process_batch(i // batch_size, batch)
multitasking.wait_for_tasks()
Pool Management
---------------
MultiTasking uses execution pools to manage concurrent tasks. You can
create and configure multiple pools for different types of operations:
.. code:: python
import multitasking
# Create a pool for API calls (higher concurrency)
multitasking.createPool("api_pool", threads=20, engine="thread")
# Create a pool for CPU-intensive tasks (lower concurrency)
multitasking.createPool("cpu_pool", threads=4, engine="process")
# Switch between pools
multitasking.use_tag("api_pool") # Future tasks use this pool
@multitasking.task
def api_call(endpoint):
# This will use the api_pool
pass
# Get pool information
pool_info = multitasking.getPool("api_pool")
print(f"Pool: {pool_info}") # {'engine': 'thread', 'name': 'api_pool', 'threads': 20}
Task Monitoring
---------------
Monitor and control your tasks with built-in functions:
.. code:: python
import multitasking
import time
@multitasking.task
def long_running_task(task_id):
time.sleep(2)
print(f"Task {task_id} completed")
# Start some tasks
for i in range(5):
long_running_task(i)
# Monitor active tasks
while multitasking.get_active_tasks():
active_count = len(multitasking.get_active_tasks())
total_count = len(multitasking.get_list_of_tasks())
print(f"Progress: {total_count - active_count}/{total_count} completed")
time.sleep(0.5)
print("All tasks finished!")
Configuration & Settings
========================
Thread/Process Limits
---------------------
The default maximum threads equals the number of CPU cores. You can
customize this:
.. code:: python
import multitasking
# Set maximum concurrent tasks
multitasking.set_max_threads(10)
# Scale based on CPU cores (good rule of thumb for I/O-bound tasks)
multitasking.set_max_threads(multitasking.config["CPU_CORES"] * 5)
# Unlimited concurrent tasks (use carefully!)
multitasking.set_max_threads(0)
Execution Engine Selection
--------------------------
Choose between threading and multiprocessing based on your use case:
.. code:: python
import multitasking
# For I/O-bound tasks (default, recommended for most cases)
multitasking.set_engine("thread")
# For CPU-bound tasks (avoids GIL limitations)
multitasking.set_engine("process")
**When to use threads vs processes:**
- **Threads** (default): Best for I/O-bound tasks like file operations,
network requests, database queries
- **Processes**: Best for CPU-intensive tasks like mathematical
computations, image processing, data analysis
Advanced Pool Configuration
---------------------------
Create specialized pools for different workloads:
.. code:: python
import multitasking
# Fast pool for quick API calls
multitasking.createPool("fast_api", threads=50, engine="thread")
# CPU pool for heavy computation
multitasking.createPool("compute", threads=2, engine="process")
# Unlimited pool for lightweight tasks
multitasking.createPool("unlimited", threads=0, engine="thread")
# Get current pool info
current_pool = multitasking.getPool()
print(f"Using pool: {current_pool['name']}")
Best Practices
==============
Performance Tips
----------------
1. **Choose the right engine**: Use threads for I/O-bound tasks,
processes for CPU-bound tasks
2. **Tune thread counts**: Start with CPU cores × 2-5 for I/O tasks, CPU
cores for CPU tasks
3. **Use pools wisely**: Create separate pools for different types of
operations
4. **Monitor memory usage**: Each thread/process consumes memory
5. **Handle exceptions**: Always wrap risky operations in try-catch
blocks
Error Handling
--------------
.. code:: python
import multitasking
import requests
@multitasking.task
def robust_fetch(url):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.Timeout:
print(f"⏰ Timeout fetching {url}")
except requests.exceptions.RequestException as e:
print(f"❌ Error fetching {url}: {e}")
except Exception as e:
print(f"💥 Unexpected error: {e}")
return None
Resource Management
-------------------
.. code:: python
import multitasking
import signal
# Graceful shutdown on interrupt
def cleanup_handler(signum, frame):
print("🛑 Shutting down gracefully...")
multitasking.wait_for_tasks()
print("✅ All tasks completed")
exit(0)
signal.signal(signal.SIGINT, cleanup_handler)
# Your application code here...
Troubleshooting
===============
Common Issues
-------------
**Tasks not running concurrently?** Check if youre calling
``wait_for_tasks()`` inside your task loop instead of after it.
**High memory usage?** Reduce the number of concurrent threads or switch
to a process-based engine.
**Tasks hanging?** Ensure your tasks can complete (avoid infinite loops)
and handle exceptions properly.
**Import errors?** Make sure youre using Python 3.6+ and have installed
the latest version.
Debugging
---------
.. code:: python
import multitasking
# Enable task monitoring
active_tasks = multitasking.get_active_tasks()
all_tasks = multitasking.get_list_of_tasks()
print(f"Active: {len(active_tasks)}, Total: {len(all_tasks)}")
# Get current pool configuration
pool_info = multitasking.getPool()
print(f"Current pool: {pool_info}")
Installation
============
**Requirements:** - Python 3.6 or higher - No external dependencies!
**Install via pip:**
.. code:: bash
$ pip install multitasking --upgrade --no-cache-dir
**Development installation:**
.. code:: bash
$ git clone https://github.com/ranaroussi/multitasking.git
$ cd multitasking
$ pip install -e .
Compatibility
=============
- **Python**: 3.6+ (type hints require 3.6+)
- **Operating Systems**: Windows, macOS, Linux
- **Environments**: Works in Jupyter notebooks, scripts, web
applications
- **Frameworks**: Compatible with Flask, Django, FastAPI, and other
Python frameworks
API Reference
=============
Decorators
----------
- ``@multitasking.task`` - Convert function to asynchronous task
Configuration Functions
-----------------------
- ``set_max_threads(count)`` - Set maximum concurrent tasks
- ``set_engine(type)`` - Choose “thread” or “process” engine
- ``createPool(name, threads, engine)`` - Create custom execution pool
Task Management
---------------
- ``wait_for_tasks(sleep=0)`` - Wait for all tasks to complete
- ``get_active_tasks()`` - Get list of running tasks
- ``get_list_of_tasks()`` - Get list of all tasks
- ``killall()`` - Emergency shutdown (force exit)
.. _pool-management-1:
Pool Management
---------------
- ``getPool(name=None)`` - Get pool information
- ``createPool(name, threads=None, engine=None)`` - Create new pool
Performance Benchmarks
======================
Heres a simple benchmark comparing synchronous vs asynchronous
execution:
.. code:: python
import multitasking
import time
import requests
# Synchronous version
def sync_fetch():
start = time.time()
for i in range(10):
requests.get("https://httpbin.org/delay/1")
print(f"Synchronous: {time.time() - start:.2f}s")
# Asynchronous version
@multitasking.task
def async_fetch():
requests.get("https://httpbin.org/delay/1")
def concurrent_fetch():
start = time.time()
for i in range(10):
async_fetch()
multitasking.wait_for_tasks()
print(f"Concurrent: {time.time() - start:.2f}s")
# Results: Synchronous ~10s, Concurrent ~1s (10x speedup!)
Contributing
============
We welcome contributions! Heres how you can help:
1. **Report bugs**: Open an issue with details and reproduction steps
2. **Suggest features**: Share your ideas for improvements
3. **Submit PRs**: Fork, create a feature branch, and submit a pull
request
4. **Improve docs**: Help make the documentation even better
**Development setup:**
.. code:: bash
$ git clone https://github.com/ranaroussi/multitasking.git
$ cd multitasking
$ pip install -e .
$ python -m pytest # Run tests
Legal Stuff
===========
**MultiTasking** is distributed under the **Apache Software License**.
See the `LICENSE.txt <./LICENSE.txt>`__ file in the release for details.
Support
=======
- 📖 **Documentation**: This README and inline code documentation
- 🐛 **Issues**: `GitHub Issues <https://github.com/ranaroussi/multitasking/issues>`__
- 🐦 **Twitter**: [@aroussi](https://twitter.com/aroussi)
Changelog
=========
**v0.0.12-rc** - ✨ Added comprehensive type hints throughout the
codebase - 📚 Enhanced documentation with detailed docstrings and inline
comments - 🔧 Improved error handling with specific exception types - 🚀
Optimized task creation and pool management logic - 🛡️ Made codebase
fully PEP8 compliant and linter-friendly - 🧹 Better code organization
and maintainability
**v0.0.11** (Latest) - Previous stable release
--------------
**Happy Multitasking! 🚀**
*Please drop me a note with any feedback you have.*
**Ran Aroussi**
.. |Python version| image:: https://img.shields.io/badge/python-3.6+-blue.svg?style=flat
:target: https://pypi.python.org/pypi/multitasking
.. |PyPi version| image:: https://img.shields.io/pypi/v/multitasking.svg?maxAge=60
:target: https://pypi.python.org/pypi/multitasking
.. |PyPi status| image:: https://img.shields.io/pypi/status/multitasking.svg?maxAge=2592000
:target: https://pypi.python.org/pypi/multitasking
.. |PyPi downloads| image:: https://img.shields.io/pypi/dm/multitasking.svg?maxAge=2592000
:target: https://pypi.python.org/pypi/multitasking
.. |CodeFactor| image:: https://www.codefactor.io/repository/github/ranaroussi/multitasking/badge
:target: https://www.codefactor.io/repository/github/ranaroussi/multitasking
.. |Star this repo| image:: https://img.shields.io/github/stars/ranaroussi/multitasking.svg?style=social&label=Star&maxAge=60
:target: https://github.com/ranaroussi/multitasking
.. |Follow me on twitter| image:: https://img.shields.io/twitter/follow/aroussi.svg?style=social&label=Follow%20Me&maxAge=60
:target: https://twitter.com/aroussi
@@ -0,0 +1,8 @@
multitasking-0.0.12.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
multitasking-0.0.12.dist-info/METADATA,sha256=dMB6Z-Iv2jOJbjVWfHQEZoyJKWLRxyctwRXqj4bkk_s,16491
multitasking-0.0.12.dist-info/RECORD,,
multitasking-0.0.12.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
multitasking-0.0.12.dist-info/licenses/LICENSE.txt,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
multitasking-0.0.12.dist-info/top_level.txt,sha256=3rgKKVaIZa_0iLRYsEdo-xxCoZKRH9mhvVQ4W6y02_M,13
multitasking/__init__.py,sha256=iJllVbEVK1j5Fr1DFqovxnLgvZMPgSNQbtPDPddgUsc,15041
multitasking/__pycache__/__init__.cpython-311.pyc,,
@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (82.0.0)
Root-Is-Purelib: true
Tag: py3-none-any
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"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
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
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
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
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 stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.