From b31b3fd2cbe96cfaa84ed81def0beed692b83016 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 11:22:05 +0800 Subject: [PATCH 01/26] docs: udpate README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 860e1b60..10faa937 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Thank you to all our sponsors! ✨🍰✨ ([become a sponsor](docs/sponsors.md)) > [霍格沃兹测试学院](https://ceshiren.com/) 是业界领先的测试开发技术高端教育品牌,隶属于测吧(北京)科技有限公司。学院课程均由 BAT 一线测试大咖执教,提供实战驱动的接口自动化测试、移动自动化测试、性能测试、持续集成与 DevOps 等技术培训,以及测试开发优秀人才内推服务。[点击学习!](https://ke.qq.com/course/254956?flowToken=1014690) -[霍格沃兹测试学院](https://ceshiren.com/) 是 HttpRunner 的首家金牌赞助商。 +霍格沃兹测试学院是 HttpRunner 的首家金牌赞助商。 ### 开源服务赞助商(Open Source Sponsor) From dcb4d2900ce665916102affbbcab81f7e51d5c57 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 11:39:51 +0800 Subject: [PATCH 02/26] docs: run testcase, log Client & Server IP:PORT --- docs/user/run_testcase.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/user/run_testcase.md b/docs/user/run_testcase.md index a9e7fe77..80cb7d95 100644 --- a/docs/user/run_testcase.md +++ b/docs/user/run_testcase.md @@ -418,6 +418,29 @@ timestamp_six_digits = str(int(time.time() * 1000))[-6:]) In other words, all requests in one testcase will have the same `HRUN-Request-ID` prefix, and each request will have a unique `HRUN-Request-ID` suffix. +## Client & Server IP:PORT + +Sometimes, logging remote server IP and port can be great helpful for trouble shooting, especially when there are multiple servers and we want to checkout which one returns error. + +Since version `3.0.13`, HttpRunner will log client & server IP:Port in debug level. + +```text +2020-06-18 11:32:38.366 | INFO | httprunner.runner:__run_step:278 - run step begin: post raw text >>>>>> +2020-06-18 11:32:38.687 | DEBUG | httprunner.client:request:187 - client IP: 10.90.205.63, Port: 62802 +2020-06-18 11:32:38.687 | DEBUG | httprunner.client:request:195 - server IP: 34.233.204.163, Port: 443 +``` + +as well as testcase summary. + +```text +"address": { + "client_ip": "10.90.205.63", + "client_port": 62802, + "server_ip": "34.233.204.163", + "server_port": 443 +}, +``` + ## arguments for v2.x compatibility Besides all the arguments of `pytest`, `hrun` also has several other arguments to keep compatibility with HttpRunner v2.x. From 7b6c8be740d7bec759f8e334230ecd6e967130c6 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 15:14:42 +0800 Subject: [PATCH 03/26] change: make compatibility with Locust HttpSession --- httprunner/__init__.py | 2 +- httprunner/runner.py | 22 ++++++++++++++-------- pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 5d1c8208..6d6902bd 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "3.0.13" +__version__ = "3.1.0" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.runner import HttpRunner diff --git a/httprunner/runner.py b/httprunner/runner.py index f2c06c25..ab9b4efa 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -203,24 +203,30 @@ class HttpRunner(object): # validate validators = step.validators + session_success = False try: resp_obj.validate( validators, variables_mapping, self.__project_meta.functions ) - self.__session.data.success = True + session_success = True except ValidationFailure: - self.__session.data.success = False + session_success = False log_req_resp_details() # log testcase duration before raise ValidationFailure self.__duration = time.time() - self.__start_at raise finally: - # save request & response meta data - self.__session.data.validators = resp_obj.validation_results - self.success = self.__session.data.success - # save step data - step_data.success = self.__session.data.success - step_data.data = self.__session.data + self.success = session_success + step_data.success = session_success + + if hasattr(self.__session, "data"): + # httprunner.client.HttpSession, not locust.clients.HttpSession + # save request & response meta data + self.__session.data.success = session_success + self.__session.data.validators = resp_obj.validation_results + + # save step data + step_data.data = self.__session.data return step_data diff --git a/pyproject.toml b/pyproject.toml index 7af26f24..542fe3ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "3.0.13" +version = "3.1.0" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 737629293f64c86f7e12d1d8b1ee0aff556da6be Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 17:19:34 +0800 Subject: [PATCH 04/26] change: add weight attribute in config --- httprunner/models.py | 1 + httprunner/testcase.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/httprunner/models.py b/httprunner/models.py index 0e20a494..edb2a559 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -41,6 +41,7 @@ class TConfig(BaseModel): # teardown_hooks: Hooks = [] export: Export = [] path: Text = None + weight: int = 1 class TRequest(BaseModel): diff --git a/httprunner/testcase.py b/httprunner/testcase.py index 03f8e68a..1efb63ec 100644 --- a/httprunner/testcase.py +++ b/httprunner/testcase.py @@ -17,18 +17,23 @@ class Config(object): self.__base_url = "" self.__verify = False self.__export = [] + self.__weight = 1 caller_frame = inspect.stack()[1] self.__path = caller_frame.filename @property - def name(self): + def name(self) -> Text: return self.__name @property - def path(self): + def path(self) -> Text: return self.__path + @property + def weight(self) -> int: + return self.__weight + def variables(self, **variables) -> "Config": self.__variables.update(variables) return self @@ -45,6 +50,10 @@ class Config(object): self.__export.extend(export_var_name) return self + def locust_weight(self, weight: int) -> "Config": + self.__weight = weight + return self + def perform(self) -> TConfig: return TConfig( name=self.__name, @@ -53,6 +62,7 @@ class Config(object): variables=self.__variables, export=list(set(self.__export)), path=self.__path, + weight=self.__weight, ) From 5c4460def452dc91a3604a0967b932128754c844 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 18:05:47 +0800 Subject: [PATCH 05/26] feat: integrate locust v1.0 --- .gitignore | 1 - docs/CHANGELOG.md | 6 + docs/installation.md | 10 +- docs/user/run_loadtest.md | 168 +++++++++ httprunner/__init__.py | 1 + httprunner/ext/locust/__init__.py | 101 +++++ httprunner/ext/locust/locustfile.py | 31 ++ mkdocs.yml | 1 + poetry.lock | 558 +++++++++++++++++++++++++--- pyproject.toml | 3 + 10 files changed, 830 insertions(+), 50 deletions(-) create mode 100644 docs/user/run_loadtest.md create mode 100644 httprunner/ext/locust/__init__.py create mode 100644 httprunner/ext/locust/locustfile.py diff --git a/.gitignore b/.gitignore index 56349609..b67029dd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ dist/* .python-version logs .coverage -locustfile.py site/ reports .venv diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index eca6a620..08509b33 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## 3.1.0 (2020-06-18) + +**Added** + +- feat: integrate [locust](https://locust.io/) v1.0 + ## 3.0.13 (2020-06-17) **Added** diff --git a/docs/installation.md b/docs/installation.md index 4f4db7ad..e3d9a884 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -24,18 +24,19 @@ $ pip3 install -U git+https://github.com/httprunner/httprunner.git@master ## Check Installation -When HttpRunner is installed, 4 commands will be added in your system. +When HttpRunner is installed, 5 commands will be added in your system. - `httprunner`: main command, used for all functions - `hrun`: alias for `httprunner run`, used to run YAML/JSON/pytest testcases - `hmake`: alias for `httprunner make`, used to convert YAML/JSON testcases to pytest files - `har2case`: alias for `httprunner har2case`, used to convert HAR to YAML/JSON testcases +- `locusts`: used to run load test with [locust][locust] To see `HttpRunner` version: ```text $ httprunner -V # hrun -V -3.0.10 +3.1.0 ``` To see available options, run: @@ -60,5 +61,8 @@ optional arguments: -V, --version show version ``` +> Notice: `locusts` is an individual command, for the reason to monkey patch ssl at beginning to avoid RecursionError when running locust. + [PyPI]: https://pypi.python.org/pypi -[github-actions]: https://github.com/httprunner/httprunner/actions \ No newline at end of file +[github-actions]: https://github.com/httprunner/httprunner/actions +[locust]: http://locust.io/ diff --git a/docs/user/run_loadtest.md b/docs/user/run_loadtest.md new file mode 100644 index 00000000..8bd35cde --- /dev/null +++ b/docs/user/run_loadtest.md @@ -0,0 +1,168 @@ +# Run load test + +> Integrated since HttpRunner 3.1.0 + +With reuse of [`Locust`][Locust], you can run load test without extra work. + +```text +$ locusts -V +locust 1.0.3 +``` + +For full usage, you can run `locusts -h` to see help, and you will find that it is the same with `locust -h`. + +The only difference is the `-f/--locustfile` argument. When you specify `-f` with a YAML/JSON testcase file, it will convert to pytest first and then run load test with HttpRunner's builtin locustfile(httprunner/ext/locust/locustfile.py). + +```text +$ locusts -f examples/postman_echo/request_methods/request_with_variables.yml +2020-06-18 18:14:29.877 | INFO | httprunner.make:make_testcase:317 - start to make testcase: /Users/debugtalk/MyProjects/HttpRunner-dev/HttpRunner/examples/postman_echo/request_methods/request_with_variables.yml +2020-06-18 18:14:29.878 | INFO | httprunner.make:make_testcase:390 - generated testcase: /Users/debugtalk/MyProjects/HttpRunner-dev/HttpRunner/examples/postman_echo/request_methods/request_with_variables_test.py +2020-06-18 18:14:29.878 | INFO | httprunner.make:format_pytest_with_black:154 - format pytest cases with black ... +reformatted /Users/debugtalk/MyProjects/HttpRunner-dev/HttpRunner/examples/postman_echo/request_methods/request_with_variables_test.py +All done! ✨ 🍰 ✨ +1 file reformatted, 1 file left unchanged. +[2020-06-18 18:14:30,241] LeodeMacBook-Pro.local/INFO/locust.main: Starting web interface at http://:8089 +[2020-06-18 18:14:30,249] LeodeMacBook-Pro.local/INFO/locust.main: Starting Locust 1.0.3 +``` + +In this case, you can reuse all features of [`Locust`][Locust]. + +```text +$ locusts -h +Usage: locust [OPTIONS] [UserClass ...] + +Common options: + -h, --help show this help message and exit + -f LOCUSTFILE, --locustfile LOCUSTFILE + Python module file to import, e.g. '../other.py'. + Default: locustfile + --config CONFIG Config file path + -H HOST, --host HOST Host to load test in the following format: + http://10.21.32.33 + -u NUM_USERS, --users NUM_USERS + Number of concurrent Locust users. Only used together + with --headless + -r HATCH_RATE, --hatch-rate HATCH_RATE + The rate per second in which users are spawned. Only + used together with --headless + -t RUN_TIME, --run-time RUN_TIME + Stop after the specified amount of time, e.g. (300s, + 20m, 3h, 1h30m, etc.). Only used together with + --headless + -l, --list Show list of possible User classes and exit + +Web UI options: + --web-host WEB_HOST Host to bind the web interface to. Defaults to '*' + (all interfaces) + --web-port WEB_PORT, -P WEB_PORT + Port on which to run web host + --headless Disable the web interface, and instead start the load + test immediately. Requires -u and -t to be specified. + --web-auth WEB_AUTH Turn on Basic Auth for the web interface. Should be + supplied in the following format: username:password + --tls-cert TLS_CERT Optional path to TLS certificate to use to serve over + HTTPS + --tls-key TLS_KEY Optional path to TLS private key to use to serve over + HTTPS + +Master options: + Options for running a Locust Master node when running Locust distributed. A Master node need Worker nodes that connect to it before it can run load tests. + + --master Set locust to run in distributed mode with this + process as master + --master-bind-host MASTER_BIND_HOST + Interfaces (hostname, ip) that locust master should + bind to. Only used when running with --master. + Defaults to * (all available interfaces). + --master-bind-port MASTER_BIND_PORT + Port that locust master should bind to. Only used when + running with --master. Defaults to 5557. + --expect-workers EXPECT_WORKERS + How many workers master should expect to connect + before starting the test (only when --headless used). + +Worker options: + + Options for running a Locust Worker node when running Locust distributed. + Only the LOCUSTFILE (-f option) need to be specified when starting a Worker, since other options such as -u, -r, -t are specified on the Master node. + + --worker Set locust to run in distributed mode with this + process as worker + --master-host MASTER_HOST + Host or IP address of locust master for distributed + load testing. Only used when running with --worker. + Defaults to 127.0.0.1. + --master-port MASTER_PORT + The port to connect to that is used by the locust + master for distributed load testing. Only used when + running with --worker. Defaults to 5557. + +Tag options: + Locust tasks can be tagged using the @tag decorator. These options let specify which tasks to include or exclude during a test. + + -T [TAG [TAG ...]], --tags [TAG [TAG ...]] + List of tags to include in the test, so only tasks + with any matching tags will be executed + -E [TAG [TAG ...]], --exclude-tags [TAG [TAG ...]] + List of tags to exclude from the test, so only tasks + with no matching tags will be executed + +Request statistics options: + --csv CSV_PREFIX Store current request stats to files in CSV format. + Setting this option will generate three files: + [CSV_PREFIX]_stats.csv, [CSV_PREFIX]_stats_history.csv + and [CSV_PREFIX]_failures.csv + --csv-full-history Store each stats entry in CSV format to + _stats_history.csv file + --print-stats Print stats in the console + --only-summary Only print the summary stats + --reset-stats Reset statistics once hatching has been completed. + Should be set on both master and workers when running + in distributed mode + +Logging options: + --skip-log-setup Disable Locust's logging setup. Instead, the + configuration is provided by the Locust test or Python + defaults. + --loglevel LOGLEVEL, -L LOGLEVEL + Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. + Default is INFO. + --logfile LOGFILE Path to log file. If not set, log will go to + stdout/stderr + +Step load options: + --step-load Enable Step Load mode to monitor how performance + metrics varies when user load increases. Requires + --step-users and --step-time to be specified. + --step-users STEP_USERS + User count to increase by step in Step Load mode. Only + used together with --step-load + --step-time STEP_TIME + Step duration in Step Load mode, e.g. (300s, 20m, 3h, + 1h30m, etc.). Only used together with --step-load + +Other options: + --show-task-ratio Print table of the User classes' task execution ratio + --show-task-ratio-json + Print json data of the User classes' task execution + ratio + --version, -V Show program's version number and exit + --exit-code-on-error EXIT_CODE_ON_ERROR + Sets the process exit code to use when a test result + contain any failure or error + -s STOP_TIMEOUT, --stop-timeout STOP_TIMEOUT + Number of seconds to wait for a simulated user to + complete any executing task before exiting. Default is + to terminate immediately. This parameter only needs to + be specified for the master process when running + Locust distributed. + +User classes: + UserClass Optionally specify which User classes that should be + used (available User classes can be listed with -l or + --list) +``` + +Enjoy! + +[Locust]: http://locust.io/ diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 6d6902bd..f3eb86ca 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,6 +1,7 @@ __version__ = "3.1.0" __description__ = "One-stop solution for HTTP(S) testing." +from httprunner.ext.locust import main_locusts # import firstly for monkey patch if needed from httprunner.runner import HttpRunner from httprunner.testcase import Config, Step, RunRequest, RunTestCase diff --git a/httprunner/ext/locust/__init__.py b/httprunner/ext/locust/__init__.py new file mode 100644 index 00000000..bd539d54 --- /dev/null +++ b/httprunner/ext/locust/__init__.py @@ -0,0 +1,101 @@ +import importlib.util +import inspect +import os +import sys +from typing import List + +if sys.argv[0].endswith("locusts"): + try: + # monkey patch ssl at beginning to avoid RecursionError when running locust. + from gevent import monkey + + monkey.patch_ssl() + from locust import main as locust_main + except ImportError: + msg = """ +Locust is not installed, install first and try again. +install with pip: +$ pip install locust +""" + print(msg) + sys.exit(1) + + +""" converted pytest files from YAML/JSON testcases +""" +pytest_files: List = [] + + +def is_httprunner_testcase(item): + """ check if a variable is a HttpRunner testcase class + """ + from httprunner import HttpRunner + # TODO: skip referenced testcase + return bool( + inspect.isclass(item) + and issubclass(item, HttpRunner) + and item.__name__ != "HttpRunner" + ) + + +def prepare_locust_tests() -> List: + """ prepare locust testcases + + Returns: + list: testcase class list + """ + + locust_tests = [] + + for pytest_file in pytest_files: + spec = importlib.util.spec_from_file_location("module.name", pytest_file) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for name, item in vars(module).items(): + + if not is_httprunner_testcase(item): + continue + + for _ in range(item.config.weight): + locust_tests.append(item) + + return locust_tests + + +def main_locusts(): + """ locusts entrance + """ + sys.argv[0] = "locust" + if len(sys.argv) == 1: + sys.argv.extend(["-h"]) + + if sys.argv[1] in ["-h", "--help", "-V", "--version"]: + locust_main.main() + + def get_arg_index(*target_args): + for arg in target_args: + if arg not in sys.argv: + continue + + return sys.argv.index(arg) + 1 + + return None + + # get testcase file path + testcase_index = get_arg_index("-f", "--locustfile") + if not testcase_index: + print("Testcase file is not specified, exit 1.") + sys.exit(1) + + from httprunner.make import main_make + global pytest_files + testcase_file_path = sys.argv[testcase_index] + pytest_files = main_make([testcase_file_path]) + if not pytest_files: + print("No valid testcases found, exit 1.") + sys.exit(1) + + sys.argv[testcase_index] = os.path.join(os.path.dirname(__file__), "locustfile.py") + + locust_main.main() diff --git a/httprunner/ext/locust/locustfile.py b/httprunner/ext/locust/locustfile.py new file mode 100644 index 00000000..77598c79 --- /dev/null +++ b/httprunner/ext/locust/locustfile.py @@ -0,0 +1,31 @@ +import random + +from locust import task, HttpUser, between + +from httprunner.ext.locust import prepare_locust_tests + + +class HttpRunnerUser(HttpUser): + host = "" + wait_time = between(5, 15) + + def on_start(self): + locust_tests = prepare_locust_tests() + self.testcase_runners = [ + testcase().with_session(self.client) + for testcase in locust_tests + ] + + @task + def test_any(self): + test_runner = random.choice(self.testcase_runners) + try: + test_runner.run() + except Exception as ex: + self.environment.events.request_failure.fire( + request_type="Failed", + name=test_runner.config.name, + response_time=0, + response_length=0, + exception=ex, + ) diff --git a/mkdocs.yml b/mkdocs.yml index d6887495..4b2ed4b5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -59,5 +59,6 @@ nav: - Write Testcase: user/write_testcase.md - Run Testcase: user/run_testcase.md - Testing Report: user/testing_report.md + - Run load test: user/run_loadtest.md - Sponsors: sponsors.md - CHANGELOG: CHANGELOG.md diff --git a/poetry.lock b/poetry.lock index 26dd749a..d1301a00 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,7 +95,19 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2020.4.5.1" +version = "2020.4.5.2" + +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +marker = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +name = "cffi" +optional = true +python-versions = "*" +version = "1.14.0" + +[package.dependencies] +pycparser = "*" [[package]] category = "main" @@ -122,6 +134,17 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.4.3" +[[package]] +category = "main" +description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." +name = "configargparse" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.2.3" + +[package.extras] +yaml = ["pyyaml"] + [[package]] category = "main" description = "PEP 567 Backport" @@ -177,6 +200,80 @@ optional = true python-versions = "*" version = "1.0.7" +[[package]] +category = "main" +description = "A simple framework for building complex web applications." +name = "flask" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.1.2" + +[package.dependencies] +Jinja2 = ">=2.10.1" +Werkzeug = ">=0.15" +click = ">=5.1" +itsdangerous = ">=0.24" + +[package.extras] +dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] +dotenv = ["python-dotenv"] + +[[package]] +category = "main" +description = "HTTP basic access authentication for Flask." +name = "flask-basicauth" +optional = true +python-versions = "*" +version = "0.2.0" + +[package.dependencies] +Flask = "*" + +[[package]] +category = "main" +description = "Coroutine-based network library" +name = "gevent" +optional = true +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +version = "20.6.2" + +[package.dependencies] +cffi = ">=1.12.2" +greenlet = ">=0.4.16" +setuptools = "*" +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0)", "idna"] +docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"] +test = ["dnspython (>=1.16.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] + +[[package]] +category = "main" +description = "http client library for gevent" +name = "geventhttpclient" +optional = true +python-versions = "*" +version = "1.4.2" + +[package.dependencies] +certifi = "*" +gevent = ">=0.13" +six = "*" + +[[package]] +category = "main" +description = "Lightweight in-process concurrent programming" +marker = "platform_python_implementation == \"CPython\"" +name = "greenlet" +optional = true +python-versions = "*" +version = "0.4.16" + [[package]] category = "dev" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" @@ -221,14 +318,22 @@ marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.6.0" +version = "1.6.1" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] -testing = ["packaging", "importlib-resources"] +testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] + +[[package]] +category = "main" +description = "Various helpers to pass data to untrusted environments and back." +name = "itsdangerous" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.1.0" [[package]] category = "main" @@ -252,6 +357,25 @@ optional = false python-versions = "*" version = "0.9.5" +[[package]] +category = "main" +description = "Developer friendly load testing framework" +name = "locust" +optional = true +python-versions = ">=3.6" +version = "1.0.3" + +[package.dependencies] +ConfigArgParse = ">=1.0" +Flask-BasicAuth = ">=0.2.0" +flask = ">=1.1.2" +gevent = ">=1.5.0" +geventhttpclient = ">=1.4.2" +msgpack = ">=0.6.2" +psutil = ">=5.6.7" +pyzmq = ">=16.0.2" +requests = ">=2.9.1" + [[package]] category = "main" description = "Python logging made (stupidly) simple" @@ -285,7 +409,15 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.3.0" +version = "8.4.0" + +[[package]] +category = "main" +description = "MessagePack (de)serializer." +name = "msgpack" +optional = true +python-versions = "*" +version = "1.0.0" [[package]] category = "main" @@ -323,13 +455,33 @@ version = ">=0.12" [package.extras] dev = ["pre-commit", "tox"] +[[package]] +category = "main" +description = "Cross-platform lib for process and system monitoring in Python." +name = "psutil" +optional = true +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "5.7.0" + +[package.extras] +enum = ["enum34"] + [[package]] category = "main" description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.1" +version = "1.8.2" + +[[package]] +category = "main" +description = "C parser in Python" +marker = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\"" +name = "pycparser" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.20" [[package]] category = "main" @@ -363,7 +515,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.2" +version = "5.4.3" [package.dependencies] atomicwrites = ">=1.0" @@ -414,13 +566,21 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "5.3.1" +[[package]] +category = "main" +description = "Python bindings for 0MQ" +name = "pyzmq" +optional = true +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +version = "19.0.1" + [[package]] category = "main" description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2020.5.14" +version = "2020.6.8" [[package]] category = "main" @@ -428,7 +588,7 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.23.0" +version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" @@ -554,11 +714,11 @@ version = "0.14.0" [[package]] category = "main" -description = "Measures number of Terminal column cells of wide-character codes" +description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" optional = false python-versions = "*" -version = "0.1.9" +version = "0.2.4" [[package]] category = "dev" @@ -568,6 +728,18 @@ optional = false python-versions = ">=3.6" version = "8.0.2" +[[package]] +category = "main" +description = "The comprehensive WSGI web application library." +name = "werkzeug" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "1.0.1" + +[package.extras] +dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] +watchdog = ["watchdog"] + [[package]] category = "main" description = "A small Python utility to set file creation time on Windows" @@ -593,12 +765,44 @@ version = "3.1.0" docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools", "func-timeout"] +[[package]] +category = "main" +description = "Very basic event publishing system" +name = "zope.event" +optional = true +python-versions = "*" +version = "4.4" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["sphinx"] +test = ["zope.testrunner"] + +[[package]] +category = "main" +description = "Interfaces for Python" +name = "zope.interface" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.1.0" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + [extras] allure = ["allure-pytest"] +locust = ["locust"] upload = ["requests-toolbelt", "filetype"] [metadata] -content-hash = "581cacf33c8afe330e5b6a965d5e16f6266718249cbdfed0d080b7536c5c4590" +content-hash = "e47b74393605334573a83551e91effda69586b32c65e1acb468901bf5596374d" python-versions = "^3.6" [metadata.files] @@ -631,8 +835,38 @@ black = [ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] certifi = [ - {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, - {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, + {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"}, + {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"}, +] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -646,6 +880,9 @@ colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] +configargparse = [ + {file = "ConfigArgParse-1.2.3.tar.gz", hash = "sha256:edd17be986d5c1ba2e307150b8e5f5107aba125f3574dddd02c85d5cdcfd37dc"}, +] contextvars = [ {file = "contextvars-2.4.tar.gz", hash = "sha256:f38c908aaa59c14335eeea12abea5f443646216c4e29380d7bf34d2018e2c39e"}, ] @@ -695,6 +932,110 @@ filetype = [ {file = "filetype-1.0.7-py2.py3-none-any.whl", hash = "sha256:353369948bb1c09b8b3ea3d78390b5586e9399bff9aab894a1dff954e31a66f6"}, {file = "filetype-1.0.7.tar.gz", hash = "sha256:da393ece8d98b47edf2dd5a85a2c8733e44b769e32c71af4cd96ed8d38d96aa7"}, ] +flask = [ + {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, + {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, +] +flask-basicauth = [ + {file = "Flask-BasicAuth-0.2.0.tar.gz", hash = "sha256:df5ebd489dc0914c224419da059d991eb72988a01cdd4b956d52932ce7d501ff"}, +] +gevent = [ + {file = "gevent-20.6.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b03890bbddbae5667f5baad517417056496ff5e92c3c7945b27cc08f55a9fcb2"}, + {file = "gevent-20.6.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1ea0d34cb78cdf37870be3bfb9330ebda89197bed9e048c14f4a90dec19a33e0"}, + {file = "gevent-20.6.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:73eb4cf3114fbb5dd801bd0b93941adfa2fa6d99e91976c20a121ea14b8b39b9"}, + {file = "gevent-20.6.2-cp27-cp27m-win32.whl", hash = "sha256:f41cc8e853ac2252bc58f6feabd74b8aae613e2d19097c5373463122f4dc08f0"}, + {file = "gevent-20.6.2-cp27-cp27m-win_amd64.whl", hash = "sha256:d3baff87d935a5eeffb0e4f7cd5ffe258d2430cd62aeee2e5396f85da07df435"}, + {file = "gevent-20.6.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7d8408854ce892f987305a0e9bf5c051f4ea29453665454396d6afb620c719b6"}, + {file = "gevent-20.6.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ea2e4584950186b71d648bde6af40dae4d4c6f43db25a732ec056b27a7a83afe"}, + {file = "gevent-20.6.2-cp35-cp35m-win32.whl", hash = "sha256:c0f4340e40e0f9dfe93a52a12ddf5b1eeda9bbc89b99bf3b9b23acab0dfae0a4"}, + {file = "gevent-20.6.2-cp35-cp35m-win_amd64.whl", hash = "sha256:13c74d6784ef5ada2666abf2bb310d27a1d14291f7cac46148f336b19f714d40"}, + {file = "gevent-20.6.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:78bd94f6f2ac366155169df3507068f6381f2ad77625633189ce183f86a57597"}, + {file = "gevent-20.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0b16dd85eddaf6acdad373ce90ed4da09ef466cbc5e0ee5932d13f099929e844"}, + {file = "gevent-20.6.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a47556cac07e31b3cef8fd701599b3b1365961fe3736471f41807ffa27c5c848"}, + {file = "gevent-20.6.2-cp36-cp36m-win32.whl", hash = "sha256:bef18b8bd3b728240b9bbd699737216b793d6c97b482431f69dcbe328ad73692"}, + {file = "gevent-20.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d0a67a20ce325f6a2068e0bd9fbf83db8a5f5ced972ed8ac5c20079a7d98c7d1"}, + {file = "gevent-20.6.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b17915b65b49a425115ddc3087484c81b1e47ce38c931d18bb14e453753e4d06"}, + {file = "gevent-20.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ebb8a545112110e3a6edf905ae1556b0538fc148c743aa7d8cfaebbbc23de31d"}, + {file = "gevent-20.6.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6c864b5604166ac8351e3128a1135b883b9e978fd24afbd75a249dcb42bc8ab5"}, + {file = "gevent-20.6.2-cp37-cp37m-win32.whl", hash = "sha256:e5ca5ee80a9d9e697c9fc22b4bbce9ad06870f83fc8e7774e5504892ef702476"}, + {file = "gevent-20.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f2a02d9004ccb18edd9eaf6f25da9a7763de41a69754d5e4d872a8cbf8bd0b72"}, + {file = "gevent-20.6.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:354f932c284fa45826b32f42927d892096cce05671b50b3ff59528230217ad47"}, + {file = "gevent-20.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:67776cb33b638a3c61a0351d9d1e8f33a46b47de619e249de1159892f9ff035c"}, + {file = "gevent-20.6.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:68764aca061bbbbade43727e797f9c28042f6d90cca5fb6514ef726d43ab00ca"}, + {file = "gevent-20.6.2-cp38-cp38-win32.whl", hash = "sha256:0f3fbb1703b10609856e5dffb0e358bf5edf57e52dc7cd7226e3f8674fdc0a0f"}, + {file = "gevent-20.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:a18d8dd9bfa994a22f30adfa0563d80f0809140045c34f85535f422813d25855"}, + {file = "gevent-20.6.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:9527087984f1659be899b3300d5d61c7c5b01d8beae106aff5160316da8bc56f"}, + {file = "gevent-20.6.2-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:76ef4c6e3332e6f7278142d791b28695adfce39735900fccef2a0f1d894f6b36"}, + {file = "gevent-20.6.2-pp27-pypy_73-win32.whl", hash = "sha256:3cb2f6978615d52e4e4e667b035c11a7272bb68b14d119faf1b138164b2f354f"}, + {file = "gevent-20.6.2.tar.gz", hash = "sha256:a23c2abf08e851c988723f6a2996d495f513a2c0dc70f9956af03af8debdb5d1"}, +] +geventhttpclient = [ + {file = "geventhttpclient-1.4.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:7b7e827418ecc926271111ef9a5f832063c28250487d77292442a94dce040ab8"}, + {file = "geventhttpclient-1.4.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6f662227cf8ec2f061d67e9bcc4e2a83ccae4b91c812a0f4a8dd4b239300860f"}, + {file = "geventhttpclient-1.4.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5c7a6211bd5747edf7e38a489369c510d06cfd293ec56be1bdd2045766075f8b"}, + {file = "geventhttpclient-1.4.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7e0686068f4dca0148e98957ac6804acb819d4a32886b75e4af8b7a4fafb0729"}, + {file = "geventhttpclient-1.4.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8d2874c86012b9c34de7a610b3e7762eca80da7591f945464afe3b024f99ea38"}, + {file = "geventhttpclient-1.4.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e089d1613fc30fd631cd82223abd8187bd9f3ccba70a58df38eb8139928c9237"}, + {file = "geventhttpclient-1.4.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f2c2d6a8c5973d1ab918443a7739ddc4bb1b3948ca676feccfe6b10076a13275"}, + {file = "geventhttpclient-1.4.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:565dff607131b04b8a2e302065f47be235a2adefe30443401f2dc3d58972d18f"}, + {file = "geventhttpclient-1.4.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bcad86491ef10b281f43a93ca65bb65aec6afc7856badc8cb4745daa4b43fe33"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:e4179375224242a64e965d709282af91491a6de40f2117df81a2a775f94d2bc3"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:49e0aed4b7cc7eef09294376c77690528e4a21a37a8a0e99efa940561183a0f5"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9324187592cbd66764a28a8f49dafb2bf68a07612bb4adf27a0777a35ba53cb6"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:b6cbcc500635b228a39c2a0b290059bdc8f7254776b6bc628bd7384ce0446125"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:682581949303c175abbaf686df819d403104ecc6b0355108c6cc473cc539a505"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:3df9cad44fddc775b06f7a9e8bea763bb8198ba426a8ce62bb79cb44bb7e419f"}, + {file = "geventhttpclient-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:052899e60f63529a5ffc581987fef2893f445a62ebdb5f51f02444b9153b9e00"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:edd11d6b22993008a98235f02344f79783d7727f05d4cd4cc638591b2cf00346"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a391afadf1d7005d1f584470c5a2b03588c23c25b42ebed6a385ffb1e5deb61b"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:80570bf577de010cb3cff704bc9df2d76b3398144e6224e35558a9f3f794f863"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:df9ce998dbaef4e4aa6d9b2bc129d69c9ec41a71df30808298f6b8cc2e3cee18"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3becac3ec54cc74caadcf87a309343d3298a8ca0ef6d3c4f7ab197be0acefdcb"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:a2ba13a6dc4343e9be64288fb89c077586350d201945565b5b882431a7cc111e"}, + {file = "geventhttpclient-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:79a302b9448fee4d2a334278c0cb679195ec0ed4de0832bf46797e90602d5832"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bef49de16b0c4551731294e37a05b55415fc49fa3c4e5b6cfac0d41fd430627d"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a6eb1aa56bc52ba0adb281f7977662db8661b1398866674cf9be559cf23e2765"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dade5b1266e10f78104a4880163b1aca1ace8c1efbf0e71887b9cff78bfbdb2f"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d09d59902ca62253a48f7a5ffbd6c591cca8a65fe15428a294f600333b891aaa"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:b9bf31aed10e40116357f6eb1f3ac997030485ea8afd75fd00644dc6ccba1614"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:9141b2ce49adb317cf036a70f4543319abfde625b92e780794d806bcc1bcd1de"}, + {file = "geventhttpclient-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:302277ba05f941da34fac3fdd9e0f13693c8460fa82fd8d40aa34151537fc171"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8f2bbbc0f1cfd8d0b5b1c839be513e32a4d94c79f3b28aed5e0443866ee1ce5"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6018fc296c4d6a1ca0cfdf3a9fb23792e2302cbf5717dd0a25ee0c084bf8216a"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b68fbaff99f8a7701477b83e829b74c0f6d878953dea468cc52601093a4806d4"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:94bf3480828190cb0cf3fae350b760aa12d9912b8eed5449ddb25867edae31f6"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28e4ef874d4153460c1e29323f543942a6b3be308c5a50c006d412f4f9e7fc9c"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-win32.whl", hash = "sha256:1dcc1995146edbcb98e6343137ee4444fa5966ae534e5fac647b32357479e93f"}, + {file = "geventhttpclient-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:355417460ce971a01f1ee0b4c53c65505faa0e1cc757ed16d8cdcbc5e66b21ab"}, + {file = "geventhttpclient-1.4.2-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:074f9914ce8320f71ffaa1d48247a5f2d551e0df4a225e50d8cf162128116a6b"}, + {file = "geventhttpclient-1.4.2-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:71bc17ffab758b800d1ca1c0ac0c1a47aa8dd7e80c88e9c6b454f7082ab03f8a"}, + {file = "geventhttpclient-1.4.2-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:a5a9e5dc95618bf1d061de6862429f90bb8740b59449a47bfdadd4cc94eebb9f"}, + {file = "geventhttpclient-1.4.2-pp27-pypy_73-win32.whl", hash = "sha256:3b18bb76b175d36205889e2c33c1746b70ab156392d6d5f382d1b2b349fd53e1"}, + {file = "geventhttpclient-1.4.2-pp36-pypy36_pp73-macosx_10_7_x86_64.whl", hash = "sha256:dfa24eaf4310fc99c796fafac527e7c839670f15144936ab885b43705f2d6086"}, + {file = "geventhttpclient-1.4.2-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:86feb2e8f2210af353fb00e85db672007e43dd993f53dab18453b0b230fd0673"}, + {file = "geventhttpclient-1.4.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d393b8a84dd9edcfb92ebfd35065a19eb589ce082dae4b61222c5835146c81c7"}, + {file = "geventhttpclient-1.4.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:0271d8ea2d4ad5b3fb753fb1df7e00aadfbe7d50d26fed714b97755f04011864"}, + {file = "geventhttpclient-1.4.2.tar.gz", hash = "sha256:967b11c4a37032f98c08f58176e4ac8de10473ab0c1f617acb8202d44b97fe21"}, +] +greenlet = [ + {file = "greenlet-0.4.16-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:80cb0380838bf4e48da6adedb0c7cd060c187bb4a75f67a5aa9ec33689b84872"}, + {file = "greenlet-0.4.16-cp27-cp27m-win32.whl", hash = "sha256:df7de669cbf21de4b04a3ffc9920bc8426cab4c61365fa84d79bf97401a8bef7"}, + {file = "greenlet-0.4.16-cp27-cp27m-win_amd64.whl", hash = "sha256:1429dc183b36ec972055e13250d96e174491559433eb3061691b446899b87384"}, + {file = "greenlet-0.4.16-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5ea034d040e6ab1d2ae04ab05a3f37dbd719c4dee3804b13903d4cc794b1336e"}, + {file = "greenlet-0.4.16-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c196a5394c56352e21cb7224739c6dd0075b69dd56f758505951d1d8d68cf8a9"}, + {file = "greenlet-0.4.16-cp35-cp35m-win32.whl", hash = "sha256:1000038ba0ea9032948e2156a9c15f5686f36945e8f9906e6b8db49f358e7b52"}, + {file = "greenlet-0.4.16-cp35-cp35m-win_amd64.whl", hash = "sha256:1b805231bfb7b2900a16638c3c8b45c694334c811f84463e52451e00c9412691"}, + {file = "greenlet-0.4.16-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e5db19d4a7d41bbeb3dd89b49fc1bc7e6e515b51bbf32589c618655a0ebe0bf0"}, + {file = "greenlet-0.4.16-cp36-cp36m-win32.whl", hash = "sha256:eac2a3f659d5f41d6bbfb6a97733bc7800ea5e906dc873732e00cebb98cec9e4"}, + {file = "greenlet-0.4.16-cp36-cp36m-win_amd64.whl", hash = "sha256:7eed31f4efc8356e200568ba05ad645525f1fbd8674f1e5be61a493e715e3873"}, + {file = "greenlet-0.4.16-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:682328aa576ec393c1872615bcb877cf32d800d4a2f150e1a5dc7e56644010b1"}, + {file = "greenlet-0.4.16-cp37-cp37m-win32.whl", hash = "sha256:3a35e33902b2e6079949feed7a2dafa5ac6f019da97bd255842bb22de3c11bf5"}, + {file = "greenlet-0.4.16-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b2a984bbfc543d144d88caad6cc7ff4a71be77102014bd617bd88cfb038727"}, + {file = "greenlet-0.4.16-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d83c1d38658b0f81c282b41238092ed89d8f93c6e342224ab73fb39e16848721"}, + {file = "greenlet-0.4.16-cp38-cp38-win32.whl", hash = "sha256:e695ac8c3efe124d998230b219eb51afb6ef10524a50b3c45109c4b77a8a3a92"}, + {file = "greenlet-0.4.16-cp38-cp38-win_amd64.whl", hash = "sha256:133ba06bad4e5f2f8bf6a0ac434e0fd686df749a86b3478903b92ec3a9c0c90b"}, + {file = "greenlet-0.4.16.tar.gz", hash = "sha256:6e06eac722676797e8fce4adb8ad3dc57a1bb3adfb0dd3fdf8306c055a38456c"}, +] h11 = [ {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, @@ -732,8 +1073,12 @@ immutables = [ {file = "immutables-0.14.tar.gz", hash = "sha256:a0a1cc238b678455145bae291d8426f732f5255537ed6a5b7645949704c70a78"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, - {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, + {file = "importlib_metadata-1.6.1-py2.py3-none-any.whl", hash = "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"}, + {file = "importlib_metadata-1.6.1.tar.gz", hash = "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545"}, +] +itsdangerous = [ + {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, + {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, ] jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, @@ -743,6 +1088,10 @@ jmespath = [ {file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"}, {file = "jmespath-0.9.5.tar.gz", hash = "sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9"}, ] +locust = [ + {file = "locust-1.0.3-py3-none-any.whl", hash = "sha256:b80deea54f470d8fd61a8f610d87041ce908924fe3fca09700376de49aa89195"}, + {file = "locust-1.0.3.tar.gz", hash = "sha256:7c2e850a84275b6f7c5ad4a8abde3488df94c08d3b52e619d9c71cc6b8c853cb"}, +] loguru = [ {file = "loguru-0.4.1-py3-none-any.whl", hash = "sha256:074b3caa6748452c1e4f2b302093c94b65d5a4c5a4d7743636b4121e06437b0e"}, {file = "loguru-0.4.1.tar.gz", hash = "sha256:a6101fd435ac89ba5205a105a26a6ede9e4ddbb4408a6e167852efca47806d11"}, @@ -778,8 +1127,28 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ - {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, - {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, + {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, + {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, +] +msgpack = [ + {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:cec8bf10981ed70998d98431cd814db0ecf3384e6b113366e7f36af71a0fca08"}, + {file = "msgpack-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aa5c057eab4f40ec47ea6f5a9825846be2ff6bf34102c560bad5cad5a677c5be"}, + {file = "msgpack-1.0.0-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:4233b7f86c1208190c78a525cd3828ca1623359ef48f78a6fea4b91bb995775a"}, + {file = "msgpack-1.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b3758dfd3423e358bbb18a7cccd1c74228dffa7a697e5be6cb9535de625c0dbf"}, + {file = "msgpack-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:25b3bc3190f3d9d965b818123b7752c5dfb953f0d774b454fd206c18fe384fb8"}, + {file = "msgpack-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:e7bbdd8e2b277b77782f3ce34734b0dfde6cbe94ddb74de8d733d603c7f9e2b1"}, + {file = "msgpack-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5dba6d074fac9b24f29aaf1d2d032306c27f04187651511257e7831733293ec2"}, + {file = "msgpack-1.0.0-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:908944e3f038bca67fcfedb7845c4a257c7749bf9818632586b53bcf06ba4b97"}, + {file = "msgpack-1.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:db685187a415f51d6b937257474ca72199f393dad89534ebbdd7d7a3b000080e"}, + {file = "msgpack-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ea41c9219c597f1d2bf6b374d951d310d58684b5de9dc4bd2976db9e1e22c140"}, + {file = "msgpack-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:e35b051077fc2f3ce12e7c6a34cf309680c63a842db3a0616ea6ed25ad20d272"}, + {file = "msgpack-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5bea44181fc8e18eed1d0cd76e355073f00ce232ff9653a0ae88cb7d9e643322"}, + {file = "msgpack-1.0.0-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c901e8058dd6653307906c5f157f26ed09eb94a850dddd989621098d347926ab"}, + {file = "msgpack-1.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:271b489499a43af001a2e42f42d876bb98ccaa7e20512ff37ca78c8e12e68f84"}, + {file = "msgpack-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7a22c965588baeb07242cb561b63f309db27a07382825fc98aecaf0827c1538e"}, + {file = "msgpack-1.0.0-cp38-cp38-win32.whl", hash = "sha256:002a0d813e1f7b60da599bdf969e632074f9eec1b96cbed8fb0973a63160a408"}, + {file = "msgpack-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:39c54fdebf5fa4dda733369012c59e7d085ebdfe35b6cf648f09d16708f1be5d"}, + {file = "msgpack-1.0.0.tar.gz", hash = "sha256:9534d5cc480d4aff720233411a1f765be90885750b07df772380b34c10ecb5c0"}, ] packaging = [ {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, @@ -793,9 +1162,26 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] +psutil = [ + {file = "psutil-5.7.0-cp27-none-win32.whl", hash = "sha256:298af2f14b635c3c7118fd9183843f4e73e681bb6f01e12284d4d70d48a60953"}, + {file = "psutil-5.7.0-cp27-none-win_amd64.whl", hash = "sha256:75e22717d4dbc7ca529ec5063000b2b294fc9a367f9c9ede1f65846c7955fd38"}, + {file = "psutil-5.7.0-cp35-cp35m-win32.whl", hash = "sha256:f344ca230dd8e8d5eee16827596f1c22ec0876127c28e800d7ae20ed44c4b310"}, + {file = "psutil-5.7.0-cp35-cp35m-win_amd64.whl", hash = "sha256:e2d0c5b07c6fe5a87fa27b7855017edb0d52ee73b71e6ee368fae268605cc3f5"}, + {file = "psutil-5.7.0-cp36-cp36m-win32.whl", hash = "sha256:a02f4ac50d4a23253b68233b07e7cdb567bd025b982d5cf0ee78296990c22d9e"}, + {file = "psutil-5.7.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1413f4158eb50e110777c4f15d7c759521703bd6beb58926f1d562da40180058"}, + {file = "psutil-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:d008ddc00c6906ec80040d26dc2d3e3962109e40ad07fd8a12d0284ce5e0e4f8"}, + {file = "psutil-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:73f35ab66c6c7a9ce82ba44b1e9b1050be2a80cd4dcc3352cc108656b115c74f"}, + {file = "psutil-5.7.0-cp38-cp38-win32.whl", hash = "sha256:60b86f327c198561f101a92be1995f9ae0399736b6eced8f24af41ec64fb88d4"}, + {file = "psutil-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:d84029b190c8a66a946e28b4d3934d2ca1528ec94764b180f7d6ea57b0e75e26"}, + {file = "psutil-5.7.0.tar.gz", hash = "sha256:685ec16ca14d079455892f25bd124df26ff9137664af445563c1bd36629b5e0e"}, +] py = [ - {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, - {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, + {file = "py-1.8.2-py2.py3-none-any.whl", hash = "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44"}, + {file = "py-1.8.2.tar.gz", hash = "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pydantic = [ {file = "pydantic-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2a6904e9f18dea58f76f16b95cba6a2f20b72d787abd84ecd67ebc526e61dce6"}, @@ -821,8 +1207,8 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"}, - {file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"}, + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] pytest-html = [ {file = "pytest-html-2.1.1.tar.gz", hash = "sha256:6a4ac391e105e391208e3eb9bd294a60dd336447fd8e1acddff3a6de7f4e57c5"}, @@ -845,32 +1231,62 @@ pyyaml = [ {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] +pyzmq = [ + {file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"}, + {file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"}, + {file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"}, + {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"}, + {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"}, + {file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"}, + {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"}, + {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"}, + {file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"}, + {file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"}, + {file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"}, + {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"}, + {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"}, + {file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"}, + {file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"}, + {file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"}, + {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"}, + {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"}, + {file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"}, + {file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"}, + {file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"}, + {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"}, + {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"}, + {file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"}, + {file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"}, + {file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"}, + {file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"}, + {file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"}, +] regex = [ - {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"}, - {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"}, - {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"}, - {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"}, - {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"}, - {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"}, - {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"}, - {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"}, - {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"}, - {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"}, - {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"}, - {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"}, + {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"}, + {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"}, + {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"}, + {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"}, + {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"}, + {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"}, + {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"}, + {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"}, + {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"}, + {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"}, + {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"}, + {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"}, ] requests = [ - {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, - {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, ] requests-toolbelt = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, @@ -934,8 +1350,8 @@ uvloop = [ {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, ] wcwidth = [ - {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, - {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, + {file = "wcwidth-0.2.4-py2.py3-none-any.whl", hash = "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f"}, + {file = "wcwidth-0.2.4.tar.gz", hash = "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"}, ] websockets = [ {file = "websockets-8.0.2-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:e906128532a14b9d264a43eb48f9b3080d53a9bda819ab45bf56b8039dc606ac"}, @@ -950,6 +1366,10 @@ websockets = [ {file = "websockets-8.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:049e694abe33f8a1d99969fee7bfc0ae6761f7fd5f297c58ea933b27dd6805f2"}, {file = "websockets-8.0.2.tar.gz", hash = "sha256:882a7266fa867a2ebb2c0baaa0f9159cabf131cf18c1b4270d79ad42f9208dc5"}, ] +werkzeug = [ + {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, + {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, +] win32-setctime = [ {file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"}, {file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"}, @@ -958,3 +1378,49 @@ zipp = [ {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, ] +"zope.event" = [ + {file = "zope.event-4.4-py2.py3-none-any.whl", hash = "sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7"}, + {file = "zope.event-4.4.tar.gz", hash = "sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf"}, +] +"zope.interface" = [ + {file = "zope.interface-5.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645a7092b77fdbc3f68d3cc98f9d3e71510e419f54019d6e282328c0dd140dcd"}, + {file = "zope.interface-5.1.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d1fe9d7d09bb07228650903d6a9dc48ea649e3b8c69b1d263419cc722b3938e8"}, + {file = "zope.interface-5.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a744132d0abaa854d1aad50ba9bc64e79c6f835b3e92521db4235a1991176813"}, + {file = "zope.interface-5.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:461d4339b3b8f3335d7e2c90ce335eb275488c587b61aca4b305196dde2ff086"}, + {file = "zope.interface-5.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:269b27f60bcf45438e8683269f8ecd1235fa13e5411de93dae3b9ee4fe7f7bc7"}, + {file = "zope.interface-5.1.0-cp27-cp27m-win32.whl", hash = "sha256:6874367586c020705a44eecdad5d6b587c64b892e34305bb6ed87c9bbe22a5e9"}, + {file = "zope.interface-5.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:8149ded7f90154fdc1a40e0c8975df58041a6f693b8f7edcd9348484e9dc17fe"}, + {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0103cba5ed09f27d2e3de7e48bb320338592e2fabc5ce1432cf33808eb2dfd8b"}, + {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b0becb75418f8a130e9d465e718316cd17c7a8acce6fe8fe07adc72762bee425"}, + {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8"}, + {file = "zope.interface-5.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4f98f70328bc788c86a6a1a8a14b0ea979f81ae6015dd6c72978f1feff70ecda"}, + {file = "zope.interface-5.1.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:af2c14efc0bb0e91af63d00080ccc067866fb8cbbaca2b0438ab4105f5e0f08d"}, + {file = "zope.interface-5.1.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6"}, + {file = "zope.interface-5.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d7804f6a71fc2dda888ef2de266727ec2f3915373d5a785ed4ddc603bbc91e08"}, + {file = "zope.interface-5.1.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:74bf0a4f9091131de09286f9a605db449840e313753949fe07c8d0fe7659ad1e"}, + {file = "zope.interface-5.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ba4261c8ad00b49d48bbb3b5af388bb7576edfc0ca50a49c11dcb77caa1d897e"}, + {file = "zope.interface-5.1.0-cp35-cp35m-win32.whl", hash = "sha256:ebb4e637a1fb861c34e48a00d03cffa9234f42bef923aec44e5625ffb9a8e8f9"}, + {file = "zope.interface-5.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:911714b08b63d155f9c948da2b5534b223a1a4fc50bb67139ab68b277c938578"}, + {file = "zope.interface-5.1.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:e74671e43ed4569fbd7989e5eecc7d06dc134b571872ab1d5a88f4a123814e9f"}, + {file = "zope.interface-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b1d2ed1cbda2ae107283befd9284e650d840f8f7568cb9060b5466d25dc48975"}, + {file = "zope.interface-5.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ef739fe89e7f43fb6494a43b1878a36273e5924869ba1d866f752c5812ae8d58"}, + {file = "zope.interface-5.1.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:eb9b92f456ff3ec746cd4935b73c1117538d6124b8617bc0fe6fda0b3816e345"}, + {file = "zope.interface-5.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:dcefc97d1daf8d55199420e9162ab584ed0893a109f45e438b9794ced44c9fd0"}, + {file = "zope.interface-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:f40db0e02a8157d2b90857c24d89b6310f9b6c3642369852cdc3b5ac49b92afc"}, + {file = "zope.interface-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:14415d6979356629f1c386c8c4249b4d0082f2ea7f75871ebad2e29584bd16c5"}, + {file = "zope.interface-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5e86c66a6dea8ab6152e83b0facc856dc4d435fe0f872f01d66ce0a2131b7f1d"}, + {file = "zope.interface-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:39106649c3082972106f930766ae23d1464a73b7d30b3698c986f74bf1256a34"}, + {file = "zope.interface-5.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8cccf7057c7d19064a9e27660f5aec4e5c4001ffcf653a47531bde19b5aa2a8a"}, + {file = "zope.interface-5.1.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:562dccd37acec149458c1791da459f130c6cf8902c94c93b8d47c6337b9fb826"}, + {file = "zope.interface-5.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:da2844fba024dd58eaa712561da47dcd1e7ad544a257482392472eae1c86d5e5"}, + {file = "zope.interface-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:1ae4693ccee94c6e0c88a4568fb3b34af8871c60f5ba30cf9f94977ed0e53ddd"}, + {file = "zope.interface-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:dd98c436a1fc56f48c70882cc243df89ad036210d871c7427dc164b31500dc11"}, + {file = "zope.interface-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b87ed2dc05cb835138f6a6e3595593fea3564d712cb2eb2de963a41fd35758c"}, + {file = "zope.interface-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:558a20a0845d1a5dc6ff87cd0f63d7dac982d7c3be05d2ffb6322a87c17fa286"}, + {file = "zope.interface-5.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b726194f938791a6691c7592c8b9e805fc6d1b9632a833b9c0640828cd49cbc"}, + {file = "zope.interface-5.1.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:60a207efcd8c11d6bbeb7862e33418fba4e4ad79846d88d160d7231fcb42a5ee"}, + {file = "zope.interface-5.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b054eb0a8aa712c8e9030065a59b5e6a5cf0746ecdb5f087cca5ec7685690c19"}, + {file = "zope.interface-5.1.0-cp38-cp38-win32.whl", hash = "sha256:27d287e61639d692563d9dab76bafe071fbeb26818dd6a32a0022f3f7ca884b5"}, + {file = "zope.interface-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:a5f8f85986197d1dd6444763c4a15c991bfed86d835a1f6f7d476f7198d5f56a"}, + {file = "zope.interface-5.1.0.tar.gz", hash = "sha256:40e4c42bd27ed3c11b2c983fecfb03356fae1209de10686d03c02c8696a1d90e"}, +] diff --git a/pyproject.toml b/pyproject.toml index 542fe3ca..5b65906c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,10 +42,12 @@ sentry-sdk = "^0.14.4" allure-pytest = {version = "^2.8.16", optional = true} requests-toolbelt = {version = "^0.9.1", optional = true} filetype = {version = "^1.0.7", optional = true} +locust = {version = "^1.0.3", optional = true} [tool.poetry.extras] allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure upload = ["requests-toolbelt", "filetype"] # pip install "httprunner[upload]", poetry install -E upload +locust = ["locust"] # pip install "httprunner[locust]", poetry install -E locust [tool.poetry.dev-dependencies] coverage = "^4.5.4" @@ -57,6 +59,7 @@ httprunner = "httprunner.cli:main" hrun = "httprunner.cli:main_hrun_alias" hmake = "httprunner.cli:main_make_alias" har2case = "httprunner.cli:main_har2case_alias" +locusts = "httprunner.ext.locust:main_locusts" [build-system] requires = ["poetry>=1.0.0"] From b0e5ccb1b6bd8e7f8a9e233eae03f40e535395f9 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 18:46:34 +0800 Subject: [PATCH 06/26] feat: make testcase with weight --- examples/postman_echo/request_methods/demo_testsuite.yml | 2 ++ .../demo_testsuite_yml/request_with_functions_test.py | 3 ++- .../request_with_testcase_reference_test.py | 3 ++- examples/postman_echo/request_methods/hardcode_test.py | 2 +- .../request_methods/request_with_functions.yml | 1 + .../request_methods/request_with_functions_test.py | 3 ++- .../request_with_testcase_reference_test.py | 2 +- .../request_methods/request_with_variables_test.py | 2 +- .../request_methods/validate_with_functions_test.py | 2 +- .../request_methods/validate_with_variables_test.py | 2 +- httprunner/__init__.py | 3 ++- httprunner/ext/locust/__init__.py | 2 ++ httprunner/ext/locust/locustfile.py | 3 +-- httprunner/make.py | 7 +++++++ 14 files changed, 26 insertions(+), 11 deletions(-) diff --git a/examples/postman_echo/request_methods/demo_testsuite.yml b/examples/postman_echo/request_methods/demo_testsuite.yml index 1825efe3..4c367a6b 100644 --- a/examples/postman_echo/request_methods/demo_testsuite.yml +++ b/examples/postman_echo/request_methods/demo_testsuite.yml @@ -6,6 +6,7 @@ testcases: - name: request with functions testcase: request_methods/request_with_functions.yml + weight: 2 variables: foo1: testcase_ref_bar11 expect_foo1: testcase_ref_bar11 @@ -13,6 +14,7 @@ testcases: - name: request with referenced testcase testcase: request_methods/request_with_testcase_reference.yml + weight: 3 variables: foo1: testcase_ref_bar12 expect_foo1: testcase_ref_bar12 diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py index 65fb3ced..99552365 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/request_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase @@ -18,6 +18,7 @@ class TestCaseRequestWithFunctions(HttpRunner): .base_url("https://postman-echo.com") .verify(False) .export(*["foo3"]) + .locust_weight(2) ) teststeps = [ diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py index 51fbf754..1cbf8a3e 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml import os @@ -26,6 +26,7 @@ class TestCaseRequestWithTestcaseReference(HttpRunner): ) .base_url("https://postman-echo.com") .verify(False) + .locust_weight(3) ) teststeps = [ diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index b9b77bd3..37d8f295 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/hardcode.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/request_with_functions.yml b/examples/postman_echo/request_methods/request_with_functions.yml index d2675cd6..b99bdbc3 100644 --- a/examples/postman_echo/request_methods/request_with_functions.yml +++ b/examples/postman_echo/request_methods/request_with_functions.yml @@ -7,6 +7,7 @@ config: expect_foo2: config_bar2 base_url: "https://postman-echo.com" verify: False + weight: 2 export: ["foo3"] teststeps: diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index 96694e06..bf25e103 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/request_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase @@ -18,6 +18,7 @@ class TestCaseRequestWithFunctions(HttpRunner): .base_url("https://postman-echo.com") .verify(False) .export(*["foo3"]) + .locust_weight(2) ) teststeps = [ diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index f7534307..fb0bba19 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml import os diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 0e54c55c..cb287b12 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/request_with_variables.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 786ab670..56c5f000 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/validate_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index ed5cc3ca..6ed3d459 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v3.0.13 +# NOTE: Generated By HttpRunner v3.1.0 # FROM: examples/postman_echo/request_methods/validate_with_variables.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/httprunner/__init__.py b/httprunner/__init__.py index f3eb86ca..f731c024 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,7 +1,8 @@ __version__ = "3.1.0" __description__ = "One-stop solution for HTTP(S) testing." -from httprunner.ext.locust import main_locusts # import firstly for monkey patch if needed +# import firstly for monkey patch if needed +from httprunner.ext.locust import main_locusts from httprunner.runner import HttpRunner from httprunner.testcase import Config, Step, RunRequest, RunTestCase diff --git a/httprunner/ext/locust/__init__.py b/httprunner/ext/locust/__init__.py index bd539d54..bf417951 100644 --- a/httprunner/ext/locust/__init__.py +++ b/httprunner/ext/locust/__init__.py @@ -30,6 +30,7 @@ def is_httprunner_testcase(item): """ check if a variable is a HttpRunner testcase class """ from httprunner import HttpRunner + # TODO: skip referenced testcase return bool( inspect.isclass(item) @@ -89,6 +90,7 @@ def main_locusts(): sys.exit(1) from httprunner.make import main_make + global pytest_files testcase_file_path = sys.argv[testcase_index] pytest_files = main_make([testcase_file_path]) diff --git a/httprunner/ext/locust/locustfile.py b/httprunner/ext/locust/locustfile.py index 77598c79..77279db1 100644 --- a/httprunner/ext/locust/locustfile.py +++ b/httprunner/ext/locust/locustfile.py @@ -12,8 +12,7 @@ class HttpRunnerUser(HttpUser): def on_start(self): locust_tests = prepare_locust_tests() self.testcase_runners = [ - testcase().with_session(self.client) - for testcase in locust_tests + testcase().with_session(self.client) for testcase in locust_tests ] @task diff --git a/httprunner/make.py b/httprunner/make.py index c4df5a93..d4804fdb 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -184,6 +184,9 @@ def make_config_chain_style(config: Dict) -> Text: if "export" in config: config_chain_style += f'.export(*{config["export"]})' + if "weight" in config: + config_chain_style += f'.locust_weight({config["weight"]})' + return config_chain_style @@ -442,6 +445,10 @@ def make_testsuite(testsuite: Dict) -> NoReturn: ) testcase_dict["config"]["variables"].update(testcase_variables) + # override weight + weight = testcase.get("weight", 1) + testcase_dict["config"]["weight"] = weight + # make testcase testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir) pytest_files_run_set.add(testcase_pytest_path) From 2c0f7f20277141e332ec3d2782d3790e86692220 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 18:53:59 +0800 Subject: [PATCH 07/26] change: report sentry when start to run locusts --- httprunner/ext/locust/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/httprunner/ext/locust/__init__.py b/httprunner/ext/locust/__init__.py index bf417951..58700dd2 100644 --- a/httprunner/ext/locust/__init__.py +++ b/httprunner/ext/locust/__init__.py @@ -67,6 +67,11 @@ def prepare_locust_tests() -> List: def main_locusts(): """ locusts entrance """ + from httprunner.utils import init_sentry_sdk + from sentry_sdk import capture_message + init_sentry_sdk() + capture_message("start to run locusts") + sys.argv[0] = "locust" if len(sys.argv) == 1: sys.argv.extend(["-h"]) From ec44faa62b8423567fe68e417f066835c2f8429a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 18 Jun 2020 19:21:57 +0800 Subject: [PATCH 08/26] change: do not raise error if failed to get client/server address info --- docs/CHANGELOG.md | 4 ++++ httprunner/client.py | 5 ++--- httprunner/exceptions.py | 4 ---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 08509b33..20c53ca5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,10 @@ - feat: integrate [locust](https://locust.io/) v1.0 +**Fixed** + +- change: do not raise error if failed to get client/server address info + ## 3.0.13 (2020-06-17) **Added** diff --git a/httprunner/client.py b/httprunner/client.py index bc31b5e1..deead393 100644 --- a/httprunner/client.py +++ b/httprunner/client.py @@ -12,7 +12,6 @@ from requests.exceptions import ( RequestException, ) -from httprunner.exceptions import NetworkFailure from httprunner.models import RequestData, ResponseData from httprunner.models import SessionData, ReqRespData from httprunner.utils import lower_dict_keys, omit_long_data @@ -186,7 +185,7 @@ class HttpSession(requests.Session): self.data.address.client_port = client_port logger.debug(f"client IP: {client_ip}, Port: {client_port}") except AttributeError as ex: - raise NetworkFailure(f"failed to get client address info: {ex}") + logger.warning(f"failed to get client address info: {ex}") try: server_ip, server_port = response.raw.connection.sock.getpeername() @@ -194,7 +193,7 @@ class HttpSession(requests.Session): self.data.address.server_port = server_port logger.debug(f"server IP: {server_ip}, Port: {server_port}") except AttributeError as ex: - raise NetworkFailure(f"failed to get server address info: {ex}") + logger.warning(f"failed to get server address info: {ex}") # get length of the response content content_size = int(dict(response.headers).get("content-length") or 0) diff --git a/httprunner/exceptions.py b/httprunner/exceptions.py index 4f2e8eb6..6559fd87 100644 --- a/httprunner/exceptions.py +++ b/httprunner/exceptions.py @@ -27,10 +27,6 @@ class TeardownHooksFailure(MyBaseFailure): pass -class NetworkFailure(MyBaseFailure): - pass - - """ error type exceptions these exceptions will mark test as error """ From e3947c055a1034894221ef444a0453cbb8e49f33 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 14:26:28 +0800 Subject: [PATCH 09/26] change: avoid print too much log details in console --- docs/CHANGELOG.md | 2 +- httprunner/ext/locust/__init__.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 20c53ca5..f01a53b0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 3.1.0 (2020-06-18) +## 3.1.0 (2020-06-19) **Added** diff --git a/httprunner/ext/locust/__init__.py b/httprunner/ext/locust/__init__.py index 58700dd2..fe528e68 100644 --- a/httprunner/ext/locust/__init__.py +++ b/httprunner/ext/locust/__init__.py @@ -21,6 +21,8 @@ $ pip install locust sys.exit(1) +from loguru import logger + """ converted pytest files from YAML/JSON testcases """ pytest_files: List = [] @@ -69,9 +71,14 @@ def main_locusts(): """ from httprunner.utils import init_sentry_sdk from sentry_sdk import capture_message + init_sentry_sdk() capture_message("start to run locusts") + # avoid print too much log details in console + logger.remove() + logger.add(sys.stderr, level="WARNING") + sys.argv[0] = "locust" if len(sys.argv) == 1: sys.argv.extend(["-h"]) From 215295cdda607fd370f68bda928bd1207fc232c1 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 15:05:58 +0800 Subject: [PATCH 10/26] fix: path handling error when har2case har file and cwd != ProjectRootDir --- docs/CHANGELOG.md | 1 + httprunner/ext/har2case/__init__.py | 15 +-------------- httprunner/ext/har2case/core.py | 23 +++++++++++++++++++++-- httprunner/make.py | 3 ++- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f01a53b0..68906264 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ **Fixed** - change: do not raise error if failed to get client/server address info +- fix: path handling error when har2case har file and cwd != ProjectRootDir ## 3.0.13 (2020-06-17) diff --git a/httprunner/ext/har2case/__init__.py b/httprunner/ext/har2case/__init__.py index 4a503962..dcc316b5 100644 --- a/httprunner/ext/har2case/__init__.py +++ b/httprunner/ext/har2case/__init__.py @@ -8,14 +8,9 @@ Usage: $ hrun har2case demo.har -2y """ -import os -import sys -from loguru import logger -from sentry_sdk import capture_message - -from httprunner.compat import ensure_path_sep from httprunner.ext.har2case.core import HarParser +from sentry_sdk import capture_message def init_har2case_parser(subparsers): @@ -56,14 +51,6 @@ def init_har2case_parser(subparsers): def main_har2case(args): har_source_file = args.har_source_file - if not har_source_file or not har_source_file.endswith(".har"): - logger.error("HAR file not specified.") - sys.exit(1) - - har_source_file = ensure_path_sep(har_source_file) - if not os.path.isfile(har_source_file): - logger.error(f"HAR file not exists: {har_source_file}") - sys.exit(1) if args.to_yaml: output_file_type = "YAML" diff --git a/httprunner/ext/har2case/core.py b/httprunner/ext/har2case/core.py index acb62951..6f8a0ab9 100644 --- a/httprunner/ext/har2case/core.py +++ b/httprunner/ext/har2case/core.py @@ -3,7 +3,9 @@ import json import os import sys import urllib.parse as urlparse +from typing import Text +from httprunner.compat import ensure_path_sep from loguru import logger from sentry_sdk import capture_exception @@ -16,9 +18,26 @@ except ImportError: JSONDecodeError = ValueError +def ensure_file_path(path: Text) -> Text: + + if not path or not path.endswith(".har"): + logger.error("HAR file not specified.") + sys.exit(1) + + path = ensure_path_sep(path) + if not os.path.isfile(path): + logger.error(f"HAR file not exists: {path}") + sys.exit(1) + + if not os.path.isabs(path): + path = os.path.join(os.getcwd(), path) + + return path + + class HarParser(object): def __init__(self, har_file_path, filter_str=None, exclude_str=None): - self.har_file_path = har_file_path + self.har_file_path = ensure_file_path(har_file_path) self.filter_str = filter_str self.exclude_str = exclude_str or "" @@ -346,7 +365,7 @@ class HarParser(object): utils.dump_yaml(testcase, output_testcase_file) else: # default to generate pytest file - testcase["config"]["path"] = self.har_file_path + testcase["config"]["path"] = os.path.join(os.getcwd(), self.har_file_path) output_testcase_file = make_testcase(testcase) format_pytest_with_black(output_testcase_file) diff --git a/httprunner/make.py b/httprunner/make.py index d4804fdb..af9b1337 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -80,7 +80,8 @@ def __ensure_absolute(path: Text) -> Text: absolute_path = os.path.join(project_meta.RootDir, path) if not os.path.isfile(absolute_path): - raise exceptions.ParamsError(f"Invalid testcase file path: {absolute_path}") + logger.error(f"Invalid testcase file path: {absolute_path}") + sys.exit(1) return absolute_path From 3e8bd4b6f0692849606235ef78224f122bf4507e Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 15:46:38 +0800 Subject: [PATCH 11/26] change: rename ensure_file_abs_path_valid --- httprunner/compat.py | 4 ++-- httprunner/loader.py | 1 + httprunner/make.py | 11 ++++++----- httprunner/models.py | 2 +- httprunner/utils.py | 13 +++++-------- tests/utils_test.py | 22 +++++++++++----------- 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index e899e3cc..594f6470 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -10,7 +10,7 @@ from loguru import logger from httprunner import exceptions from httprunner.loader import load_project_meta from httprunner.parser import parse_data -from httprunner.utils import sort_dict_by_custom_order, ensure_file_path_valid +from httprunner.utils import sort_dict_by_custom_order, ensure_file_abs_path_valid def convert_variables( @@ -260,7 +260,7 @@ def generate_conftest_for_summary(args: List): sys.exit(1) project_meta = load_project_meta(test_path) - project_root_dir = ensure_file_path_valid(project_meta.RootDir) + project_root_dir = ensure_file_abs_path_valid(project_meta.RootDir) conftest_path = os.path.join(project_root_dir, "conftest.py") conftest_content = '''# NOTICE: Generated By HttpRunner. import json diff --git a/httprunner/loader.py b/httprunner/loader.py index cc6799c0..c1cbe835 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -287,6 +287,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text: file_path = os.path.join(start_dir_path, file_name) if os.path.isfile(file_path): + # ensure absolute return os.path.abspath(file_path) # system root dir diff --git a/httprunner/make.py b/httprunner/make.py index af9b1337..f3d39428 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -23,7 +23,7 @@ from httprunner.loader import ( load_project_meta, ) from httprunner.response import uniform_validator -from httprunner.utils import ensure_file_path_valid, override_config_variables +from httprunner.utils import ensure_file_abs_path_valid, override_config_variables """ cache converted pytest files, avoid duplicate making """ @@ -121,7 +121,7 @@ def __ensure_project_meta_files(tests_path: Text) -> NoReturn: # handle cases when generated pytest directory are different from original yaml/json testcases debugtalk_path = project_meta.debugtalk_path if debugtalk_path: - debugtalk_new_path = ensure_file_path_valid(debugtalk_path) + debugtalk_new_path = ensure_file_abs_path_valid(debugtalk_path) if debugtalk_new_path != debugtalk_path: logger.info(f"copy debugtalk.py to {debugtalk_new_path}") copyfile(debugtalk_path, debugtalk_new_path) @@ -131,7 +131,7 @@ def __ensure_project_meta_files(tests_path: Text) -> NoReturn: dot_csv_path = project_meta.dot_env_path if dot_csv_path: - dot_csv_new_path = ensure_file_path_valid(dot_csv_path) + dot_csv_new_path = ensure_file_abs_path_valid(dot_csv_path) if dot_csv_new_path != dot_csv_path: logger.info(f"copy .env to {dot_csv_new_path}") copyfile(dot_csv_path, dot_csv_new_path) @@ -139,7 +139,7 @@ def __ensure_project_meta_files(tests_path: Text) -> NoReturn: def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: """convert single YAML/JSON testcase path to python file""" - testcase_new_path = ensure_file_path_valid(testcase_path) + testcase_new_path = ensure_file_abs_path_valid(testcase_path) dir_path = os.path.dirname(testcase_new_path) file_name, _ = os.path.splitext(os.path.basename(testcase_new_path)) @@ -410,7 +410,7 @@ def make_testsuite(testsuite: Dict) -> NoReturn: logger.info(f"start to make testsuite: {testsuite_path}") # create directory with testsuite file name, put its testcases under this directory - testsuite_path = ensure_file_path_valid(testsuite_path) + testsuite_path = ensure_file_abs_path_valid(testsuite_path) testsuite_dir, file_suffix = os.path.splitext(testsuite_path) # demo_testsuite.yml => demo_testsuite_yml testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}" @@ -497,6 +497,7 @@ def __make(tests_path: Text) -> NoReturn: ) continue + # ensure path absolute test_content.setdefault("config", {})["path"] = test_file # testcase diff --git a/httprunner/models.py b/httprunner/models.py index edb2a559..fa9d492d 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -86,7 +86,7 @@ class ProjectMeta(BaseModel): dot_env_path: Text = "" # .env file path functions: FunctionsMapping = {} # functions defined in debugtalk.py env: Env = {} - RootDir: Text = os.getcwd() # project root directory, the path debugtalk.py located + RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located class TestsMapping(BaseModel): diff --git a/httprunner/utils.py b/httprunner/utils.py index b2d0e3d7..0b22e049 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -181,26 +181,23 @@ def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List): ) -def ensure_file_path_valid(file_path: Text) -> Text: +def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space Args: - file_path: absolute or relative file path + file_abs_path: absolute file path Returns: ensured valid absolute file path """ - raw_file_name, file_suffix = os.path.splitext(file_path) + raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path) file_suffix = file_suffix.lower() - if os.path.isabs(file_path): - raw_file_relative_name = raw_file_name[len(os.getcwd()) + 1 :] - else: - raw_file_relative_name = raw_file_name + raw_file_relative_name = raw_abs_file_name[len(os.getcwd()) + 1 :] if raw_file_relative_name == "": - return file_path + return file_abs_path path_names = [] for name in raw_file_relative_name.rstrip(os.sep).split(os.sep): diff --git a/tests/utils_test.py b/tests/utils_test.py index bf81cfcc..bd4e3f53 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -5,7 +5,7 @@ import unittest from httprunner import loader, utils from httprunner.utils import ( - ensure_file_path_valid, + ensure_file_abs_path_valid, ExtendJSONEncoder, override_config_variables, ) @@ -107,36 +107,36 @@ class TestUtils(unittest.TestCase): def test_ensure_file_path_valid(self): self.assertEqual( - ensure_file_path_valid( - os.path.join("examples", "a-b.c", "d f", "hardcode.yml") + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "examples", "a-b.c", "d f", "hardcode.yml") ), os.path.join(os.getcwd(), "examples", "a_b_c", "d_f", "hardcode.yml"), ) self.assertEqual( - ensure_file_path_valid(os.path.join("1", "2B", "3.yml")), + ensure_file_abs_path_valid(os.path.join(os.getcwd(), "1", "2B", "3.yml")), os.path.join(os.getcwd(), "T1", "T2B", "T3.yml"), ) self.assertEqual( - ensure_file_path_valid( - os.path.join("examples", "a-b.c", "2B", "hardcode.yml") + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "examples", "a-b.c", "2B", "hardcode.yml") ), os.path.join(os.getcwd(), "examples", "a_b_c", "T2B", "hardcode.yml"), ) self.assertEqual( - ensure_file_path_valid( - os.path.join("examples", "postman_echo", "request_methods") + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods") ), os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods"), ) self.assertEqual( - ensure_file_path_valid(os.path.join(os.getcwd(), "test.yml")), + ensure_file_abs_path_valid(os.path.join(os.getcwd(), "test.yml")), os.path.join(os.getcwd(), "test.yml"), ) self.assertEqual( - ensure_file_path_valid(os.getcwd()), os.getcwd(), + ensure_file_abs_path_valid(os.getcwd()), os.getcwd(), ) self.assertEqual( - ensure_file_path_valid(os.path.join(os.getcwd(), "demo", ".csv")), + ensure_file_abs_path_valid(os.path.join(os.getcwd(), "demo", ".csv")), os.path.join(os.getcwd(), "demo", ".csv"), ) From 429d36b33327345e77cb4fa4173a29676284537a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 18:28:32 +0800 Subject: [PATCH 12/26] fix: missing list type for request body --- docs/CHANGELOG.md | 1 + httprunner/models.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 68906264..272d8a77 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,7 @@ - change: do not raise error if failed to get client/server address info - fix: path handling error when har2case har file and cwd != ProjectRootDir +- fix: missing list type for request body ## 3.0.13 (2020-06-17) diff --git a/httprunner/models.py b/httprunner/models.py index fa9d492d..775a7a75 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -123,7 +123,7 @@ class RequestData(BaseModel): url: Url headers: Headers = {} cookies: Cookies = {} - body: Union[Text, bytes, Dict, None] = {} + body: Union[Text, bytes, Dict, List, None] = {} class ResponseData(BaseModel): From 609a1d586d60d35d855198dab7c7525e853fcfe1 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 18:32:45 +0800 Subject: [PATCH 13/26] change: keep project root dir --- httprunner/compat.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index 594f6470..e9a77800 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -5,12 +5,11 @@ import os import sys from typing import List, Dict, Text, Union, Any -from loguru import logger - from httprunner import exceptions from httprunner.loader import load_project_meta from httprunner.parser import parse_data -from httprunner.utils import sort_dict_by_custom_order, ensure_file_abs_path_valid +from httprunner.utils import sort_dict_by_custom_order +from loguru import logger def convert_variables( @@ -260,7 +259,7 @@ def generate_conftest_for_summary(args: List): sys.exit(1) project_meta = load_project_meta(test_path) - project_root_dir = ensure_file_abs_path_valid(project_meta.RootDir) + project_root_dir = project_meta.RootDir conftest_path = os.path.join(project_root_dir, "conftest.py") conftest_content = '''# NOTICE: Generated By HttpRunner. import json From 173a52ef677722b43361ce083961c50c664631c6 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 19:20:44 +0800 Subject: [PATCH 14/26] fix: handle teststeps format error in ensure_testcase_v3 --- httprunner/compat.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/httprunner/compat.py b/httprunner/compat.py index e9a77800..7b11f130 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -206,6 +206,16 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict: def ensure_testcase_v3(test_content: Dict) -> Dict: v3_content = {"config": test_content["config"], "teststeps": []} + if "teststeps" not in test_content: + logger.error(f"Miss teststeps: {test_content}") + sys.exit(1) + + if not isinstance(test_content["teststeps"], list): + logger.error( + f'teststeps should be list type, got {type(test_content["teststeps"])}: {test_content["teststeps"]}' + ) + sys.exit(1) + for step in test_content["teststeps"]: teststep = {} From e64490f4eea84afc6311f321657deeb937a35d28 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 22:27:54 +0800 Subject: [PATCH 15/26] fix: typo error --- httprunner/ext/har2case/core.py | 2 +- httprunner/ext/uploader/__init__.py | 7 +++---- httprunner/make.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/httprunner/ext/har2case/core.py b/httprunner/ext/har2case/core.py index 6f8a0ab9..7535270b 100644 --- a/httprunner/ext/har2case/core.py +++ b/httprunner/ext/har2case/core.py @@ -365,7 +365,7 @@ class HarParser(object): utils.dump_yaml(testcase, output_testcase_file) else: # default to generate pytest file - testcase["config"]["path"] = os.path.join(os.getcwd(), self.har_file_path) + testcase["config"]["path"] = self.har_file_path output_testcase_file = make_testcase(testcase) format_pytest_with_black(output_testcase_file) diff --git a/httprunner/ext/uploader/__init__.py b/httprunner/ext/uploader/__init__.py index bcefd6a8..ef046f14 100644 --- a/httprunner/ext/uploader/__init__.py +++ b/httprunner/ext/uploader/__init__.py @@ -46,10 +46,9 @@ import os import sys from typing import Text, NoReturn -from loguru import logger - -from httprunner.parser import parse_variables_mapping from httprunner.models import TStep, FunctionsMapping +from httprunner.parser import parse_variables_mapping +from loguru import logger try: import filetype @@ -146,7 +145,7 @@ def multipart_encoder(**kwargs): # value is not absolute file path, check if it is relative file path from httprunner.loader import load_project_meta - project_meta = load_project_meta(os.getcwd()) + project_meta = load_project_meta("") _file_path = os.path.join(project_meta.RootDir, value) is_exists_file = os.path.isfile(_file_path) diff --git a/httprunner/make.py b/httprunner/make.py index f3d39428..4173254d 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -361,7 +361,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase - ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) + 1 :] + ref_testcase_python_path = __ensure_cwd_relative(ref_testcase_python_path) ref_module_name, _ = os.path.splitext(ref_testcase_python_path) ref_module_name = ref_module_name.replace(os.sep, ".") imports_list.append( From fa7464fca8a54c8868e338d9ae0841701a2a924b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 23:27:17 +0800 Subject: [PATCH 16/26] change: make FROM path in testcase notes relative to project_meta.RootDir --- examples/httpbin/basic_test.py | 4 ++-- examples/httpbin/hooks_test.py | 4 ++-- examples/httpbin/load_image_test.py | 4 ++-- examples/httpbin/upload_test.py | 4 ++-- examples/httpbin/validate_test.py | 4 ++-- .../request_with_functions_test.py | 2 +- .../request_with_testcase_reference_test.py | 2 +- .../request_methods/hardcode_test.py | 2 +- .../request_with_functions_test.py | 2 +- .../request_with_testcase_reference_test.py | 2 +- .../request_with_variables_test.py | 2 +- .../validate_with_functions_test.py | 2 +- .../validate_with_variables_test.py | 2 +- httprunner/make.py | 24 +++++++++++++++++-- 14 files changed, 40 insertions(+), 20 deletions(-) diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index 9b0241ba..3f912332 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,5 +1,5 @@ -# NOTE: Generated By HttpRunner v3.0.13 -# FROM: examples/httpbin/basic.yml +# NOTE: Generated By HttpRunner v3.1.0 +# FROM: basic.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index 2ab42e00..b9d12e1a 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,5 +1,5 @@ -# NOTE: Generated By HttpRunner v3.0.13 -# FROM: examples/httpbin/hooks.yml +# NOTE: Generated By HttpRunner v3.1.0 +# FROM: hooks.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index 50ad9b26..ecbd0aa5 100644 --- a/examples/httpbin/load_image_test.py +++ b/examples/httpbin/load_image_test.py @@ -1,5 +1,5 @@ -# NOTE: Generated By HttpRunner v3.0.13 -# FROM: examples/httpbin/load_image.yml +# NOTE: Generated By HttpRunner v3.1.0 +# FROM: load_image.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index c9c04640..826aae8f 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,5 +1,5 @@ -# NOTE: Generated By HttpRunner v3.0.13 -# FROM: examples/httpbin/upload.yml +# NOTE: Generated By HttpRunner v3.1.0 +# FROM: upload.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index dd1ff6f3..bbbc58e8 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,5 +1,5 @@ -# NOTE: Generated By HttpRunner v3.0.13 -# FROM: examples/httpbin/validate.yml +# NOTE: Generated By HttpRunner v3.1.0 +# FROM: validate.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py index 99552365..912fddad 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/request_with_functions.yml +# FROM: request_methods/request_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py index 1cbf8a3e..ee4ddeb7 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml +# FROM: request_methods/request_with_testcase_reference.yml import os import sys diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index 37d8f295..95e1e421 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/hardcode.yml +# FROM: request_methods/hardcode.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index bf25e103..68add82e 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/request_with_functions.yml +# FROM: request_methods/request_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index fb0bba19..5fe21f9a 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml +# FROM: request_methods/request_with_testcase_reference.yml import os import sys diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index cb287b12..74f38b37 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/request_with_variables.yml +# FROM: request_methods/request_with_variables.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 56c5f000..68179d7b 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/validate_with_functions.yml +# FROM: request_methods/validate_with_functions.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 6ed3d459..83225fa5 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v3.1.0 -# FROM: examples/postman_echo/request_methods/validate_with_variables.yml +# FROM: request_methods/validate_with_variables.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase diff --git a/httprunner/make.py b/httprunner/make.py index 4173254d..856afa49 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -101,6 +101,26 @@ def __ensure_cwd_relative(path: Text) -> Text: return path +def __convert_relative_project_root_dir(abs_path: Text) -> Text: + """ convert absolute path to relative path, based on project_meta.RootDir + + Args: + abs_path: absolute path + + Returns: relative path based on project_meta.RootDir + + """ + project_meta = load_project_meta(abs_path) + if not abs_path.startswith(project_meta.RootDir): + raise exceptions.ParamsError( + f"failed to convert absolute path to relative path based on project_meta.RootDir\n" + f"abs_path: {abs_path}\n" + f"project_meta.RootDir: {project_meta.RootDir}" + ) + + return abs_path[len(project_meta.RootDir) + 1 :] + + def __ensure_testcase_module(path: Text) -> NoReturn: """ ensure pytest files are in python module, generate __init__.py on demand """ @@ -331,7 +351,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: return testcase_python_path config = testcase["config"] - config["path"] = __ensure_cwd_relative(testcase_python_path) + config["path"] = __convert_relative_project_root_dir(testcase_python_path) config["variables"] = convert_variables( config.get("variables", {}), testcase_abs_path ) @@ -370,7 +390,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: data = { "version": __version__, - "testcase_path": __ensure_cwd_relative(testcase_abs_path), + "testcase_path": __convert_relative_project_root_dir(testcase_abs_path), "class_name": f"TestCase{testcase_cls_name}", "imports_list": imports_list, "config_chain_style": make_config_chain_style(config), From d0e6f2930411c89c42ed7ca5c1119c552cc4314f Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 23:35:59 +0800 Subject: [PATCH 17/26] change: rename variable testcase_python_abs_path --- httprunner/make.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/httprunner/make.py b/httprunner/make.py index 856afa49..23b92d5b 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -163,12 +163,12 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: dir_path = os.path.dirname(testcase_new_path) file_name, _ = os.path.splitext(os.path.basename(testcase_new_path)) - testcase_python_path = os.path.join(dir_path, f"{file_name}_test.py") + testcase_python_abs_path = os.path.join(dir_path, f"{file_name}_test.py") # convert title case, e.g. request_with_variables => RequestWithVariables name_in_title_case = file_name.title().replace("_", "") - return testcase_python_path, name_in_title_case + return testcase_python_abs_path, name_in_title_case def format_pytest_with_black(*python_paths: Text) -> NoReturn: @@ -340,18 +340,18 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: testcase_abs_path = __ensure_absolute(testcase["config"]["path"]) logger.info(f"start to make testcase: {testcase_abs_path}") - testcase_python_path, testcase_cls_name = convert_testcase_path(testcase_abs_path) + testcase_python_abs_path, testcase_cls_name = convert_testcase_path(testcase_abs_path) if dir_path: - testcase_python_path = os.path.join( - dir_path, os.path.basename(testcase_python_path) + testcase_python_abs_path = os.path.join( + dir_path, os.path.basename(testcase_python_abs_path) ) global pytest_files_made_cache_mapping - if testcase_python_path in pytest_files_made_cache_mapping: - return testcase_python_path + if testcase_python_abs_path in pytest_files_made_cache_mapping: + return testcase_python_abs_path config = testcase["config"] - config["path"] = __convert_relative_project_root_dir(testcase_python_path) + config["path"] = __convert_relative_project_root_dir(testcase_python_abs_path) config["variables"] = convert_variables( config.get("variables", {}), testcase_abs_path ) @@ -401,19 +401,19 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: content = __TEMPLATE__.render(data) # ensure new file's directory exists - dir_path = os.path.dirname(testcase_python_path) + dir_path = os.path.dirname(testcase_python_abs_path) if not os.path.exists(dir_path): os.makedirs(dir_path) - with open(testcase_python_path, "w", encoding="utf-8") as f: + with open(testcase_python_abs_path, "w", encoding="utf-8") as f: f.write(content) - pytest_files_made_cache_mapping[testcase_python_path] = testcase_cls_name - __ensure_testcase_module(testcase_python_path) + pytest_files_made_cache_mapping[testcase_python_abs_path] = testcase_cls_name + __ensure_testcase_module(testcase_python_abs_path) - logger.info(f"generated testcase: {testcase_python_path}") + logger.info(f"generated testcase: {testcase_python_abs_path}") - return testcase_python_path + return testcase_python_abs_path def make_testsuite(testsuite: Dict) -> NoReturn: From 1b5d7386a78e0002529ad0aa302f5e504561703a Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 19 Jun 2020 23:52:36 +0800 Subject: [PATCH 18/26] change: __convert_relative_current_working_dir --- httprunner/make.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/httprunner/make.py b/httprunner/make.py index 23b92d5b..8c9875f0 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -86,19 +86,24 @@ def __ensure_absolute(path: Text) -> Text: return absolute_path -def __ensure_cwd_relative(path: Text) -> Text: +def __convert_relative_current_working_dir(abs_path: Text) -> Text: """ convert absolute path to relative path, based on os.getcwd() Args: - path: absolute path + abs_path: absolute path Returns: relative path based on os.getcwd() """ - if os.path.isabs(path): - return path[len(os.getcwd()) + 1 :] - else: - return path + cwd = os.getcwd() + if not abs_path.startswith(cwd): + raise exceptions.ParamsError( + f"failed to convert absolute path to relative path based on os.getcwd()\n" + f"abs_path: {abs_path}\n" + f"os.getcwd(): {cwd}" + ) + + return abs_path[len(cwd) + 1 :] def __convert_relative_project_root_dir(abs_path: Text) -> Text: @@ -340,7 +345,9 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: testcase_abs_path = __ensure_absolute(testcase["config"]["path"]) logger.info(f"start to make testcase: {testcase_abs_path}") - testcase_python_abs_path, testcase_cls_name = convert_testcase_path(testcase_abs_path) + testcase_python_abs_path, testcase_cls_name = convert_testcase_path( + testcase_abs_path + ) if dir_path: testcase_python_abs_path = os.path.join( dir_path, os.path.basename(testcase_python_abs_path) @@ -372,17 +379,19 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: test_content = ensure_testcase_v3_api(test_content) test_content.setdefault("config", {})["path"] = ref_testcase_path - ref_testcase_python_path = make_testcase(test_content) + ref_testcase_python_abs_path = make_testcase(test_content) # prepare ref testcase class name ref_testcase_cls_name = pytest_files_made_cache_mapping[ - ref_testcase_python_path + ref_testcase_python_abs_path ] teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase - ref_testcase_python_path = __ensure_cwd_relative(ref_testcase_python_path) - ref_module_name, _ = os.path.splitext(ref_testcase_python_path) + ref_testcase_python_relative_path = __convert_relative_current_working_dir( + ref_testcase_python_abs_path + ) + ref_module_name, _ = os.path.splitext(ref_testcase_python_relative_path) ref_module_name = ref_module_name.replace(os.sep, ".") imports_list.append( f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" From 64b7659a12cd8a9303be3c438dcbc2069d58a4f3 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 20 Jun 2020 00:05:10 +0800 Subject: [PATCH 19/26] fix: unittests for convert_testcase_path --- httprunner/make.py | 4 ++-- tests/make_test.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/httprunner/make.py b/httprunner/make.py index 8c9875f0..99bf025b 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -162,9 +162,9 @@ def __ensure_project_meta_files(tests_path: Text) -> NoReturn: copyfile(dot_csv_path, dot_csv_new_path) -def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: +def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]: """convert single YAML/JSON testcase path to python file""" - testcase_new_path = ensure_file_abs_path_valid(testcase_path) + testcase_new_path = ensure_file_abs_path_valid(testcase_abs_path) dir_path = os.path.dirname(testcase_new_path) file_name, _ = os.path.splitext(os.path.basename(testcase_new_path)) diff --git a/tests/make_test.py b/tests/make_test.py index b379f901..b7f1ec68 100644 --- a/tests/make_test.py +++ b/tests/make_test.py @@ -92,7 +92,7 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( def test_convert_testcase_path(self): self.assertEqual( - convert_testcase_path("mubu.login.yml"), + convert_testcase_path(os.path.join(os.getcwd(), "mubu.login.yml")), (os.path.join(os.getcwd(), "mubu_login_test.py"), "MubuLogin"), ) self.assertEqual( @@ -107,7 +107,9 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( ), ) self.assertEqual( - convert_testcase_path(os.path.join("path", "to 2", "mubu.login.yml")), + convert_testcase_path( + os.path.join(os.getcwd(), "path", "to 2", "mubu.login.yml") + ), ( os.path.join( os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py") @@ -116,7 +118,9 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( ), ) self.assertEqual( - convert_testcase_path(os.path.join("path", "to-2", "mubu login.yml")), + convert_testcase_path( + os.path.join(os.getcwd(), "path", "to-2", "mubu login.yml") + ), ( os.path.join( os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py") @@ -125,7 +129,9 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( ), ) self.assertEqual( - convert_testcase_path(os.path.join("path", "to.2", "幕布login.yml")), + convert_testcase_path( + os.path.join(os.getcwd(), "path", "to.2", "幕布login.yml") + ), ( os.path.join( os.getcwd(), os.path.join("path", "to_2", "幕布login_test.py") From 4f405d162015375c3a758daf22f7fa8853e803f3 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 20 Jun 2020 00:33:42 +0800 Subject: [PATCH 20/26] change: code location --- httprunner/compat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index 7b11f130..966d4e89 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -268,9 +268,6 @@ def generate_conftest_for_summary(args: List): logger.error(f"No valid test path specified! \nargs: {args}") sys.exit(1) - project_meta = load_project_meta(test_path) - project_root_dir = project_meta.RootDir - conftest_path = os.path.join(project_root_dir, "conftest.py") conftest_content = '''# NOTICE: Generated By HttpRunner. import json import os @@ -337,6 +334,10 @@ def session_fixture(request): ''' + project_meta = load_project_meta(test_path) + project_root_dir = project_meta.RootDir + conftest_path = os.path.join(project_root_dir, "conftest.py") + test_path = os.path.abspath(test_path) logs_dir_path = os.path.join(project_root_dir, "logs") test_path_relative_path = test_path[len(project_root_dir) + 1 :] From 18e9b772fd8005d877945d6a0f20f5ad1345c43b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 20 Jun 2020 23:53:45 +0800 Subject: [PATCH 21/26] change: make converted referenced pytest files always relative to project RootDir --- docs/CHANGELOG.md | 4 + .../request_with_testcase_reference_test.py | 6 +- .../request_with_testcase_reference_test.py | 6 +- httprunner/compat.py | 4 +- httprunner/loader.py | 20 ++++ httprunner/make.py | 108 ++++++++---------- httprunner/utils.py | 45 +------- tests/cli_test.py | 17 +-- tests/data/{a-b.c/__init__.py => .csv} | 0 tests/data/a-b.c/2 3.yml | 2 +- tests/data/a-b.c/中文case.yml | 0 tests/data/{a-b.c => }/debugtalk.py | 0 tests/make_test.py | 75 ++++++------ tests/runner_test.py | 2 +- tests/utils_test.py | 36 ------ 15 files changed, 132 insertions(+), 193 deletions(-) rename tests/data/{a-b.c/__init__.py => .csv} (100%) create mode 100644 tests/data/a-b.c/中文case.yml rename tests/data/{a-b.c => }/debugtalk.py (100%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 272d8a77..f82a34e1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,10 @@ - feat: integrate [locust](https://locust.io/) v1.0 +**Changed** + +- change: make converted referenced pytest files always relative to ProjectRootDir + **Fixed** - change: do not raise error if failed to get client/server address info diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py index ee4ddeb7..63a5b73e 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py @@ -1,14 +1,14 @@ # NOTE: Generated By HttpRunner v3.1.0 # FROM: request_methods/request_with_testcase_reference.yml -import os import sys +from pathlib import Path -sys.path.insert(0, os.getcwd()) +sys.path.insert(0, str(Path(__file__).parent.parent)) from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase -from examples.postman_echo.request_methods.request_with_functions_test import ( +from request_methods.request_with_functions_test import ( TestCaseRequestWithFunctions as RequestWithFunctions, ) diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 5fe21f9a..7154992b 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,14 +1,14 @@ # NOTE: Generated By HttpRunner v3.1.0 # FROM: request_methods/request_with_testcase_reference.yml -import os import sys +from pathlib import Path -sys.path.insert(0, os.getcwd()) +sys.path.insert(0, str(Path(__file__).parent.parent)) from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase -from examples.postman_echo.request_methods.request_with_functions_test import ( +from request_methods.request_with_functions_test import ( TestCaseRequestWithFunctions as RequestWithFunctions, ) diff --git a/httprunner/compat.py b/httprunner/compat.py index 966d4e89..17766ed9 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -6,7 +6,7 @@ import sys from typing import List, Dict, Text, Union, Any from httprunner import exceptions -from httprunner.loader import load_project_meta +from httprunner.loader import load_project_meta, convert_relative_project_root_dir from httprunner.parser import parse_data from httprunner.utils import sort_dict_by_custom_order from loguru import logger @@ -340,7 +340,7 @@ def session_fixture(request): test_path = os.path.abspath(test_path) logs_dir_path = os.path.join(project_root_dir, "logs") - test_path_relative_path = test_path[len(project_root_dir) + 1 :] + test_path_relative_path = convert_relative_project_root_dir(test_path) if os.path.isdir(test_path): file_foder_path = os.path.join(logs_dir_path, test_path_relative_path) diff --git a/httprunner/loader.py b/httprunner/loader.py index c1cbe835..1a9e6484 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -432,3 +432,23 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta: project_meta.debugtalk_path = debugtalk_path return project_meta + + +def convert_relative_project_root_dir(abs_path: Text) -> Text: + """ convert absolute path to relative path, based on project_meta.RootDir + + Args: + abs_path: absolute path + + Returns: relative path based on project_meta.RootDir + + """ + _project_meta = load_project_meta(abs_path) + if not abs_path.startswith(_project_meta.RootDir): + raise exceptions.ParamsError( + f"failed to convert absolute path to relative path based on project_meta.RootDir\n" + f"abs_path: {abs_path}\n" + f"project_meta.RootDir: {_project_meta.RootDir}" + ) + + return abs_path[len(_project_meta.RootDir) + 1 :] diff --git a/httprunner/make.py b/httprunner/make.py index 99bf025b..b7a908d2 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -1,7 +1,7 @@ import os +import string import subprocess import sys -from shutil import copyfile from typing import Text, List, Tuple, Dict, Set, NoReturn import jinja2 @@ -21,9 +21,10 @@ from httprunner.loader import ( load_testcase, load_testsuite, load_project_meta, + convert_relative_project_root_dir, ) from httprunner.response import uniform_validator -from httprunner.utils import ensure_file_abs_path_valid, override_config_variables +from httprunner.utils import override_config_variables """ cache converted pytest files, avoid duplicate making """ @@ -36,11 +37,15 @@ pytest_files_run_set: Set = set() __TEMPLATE__ = jinja2.Template( """# NOTE: Generated By HttpRunner v{{ version }} # FROM: {{ testcase_path }} -{% if imports_list %} -import os +{% if imports_list and diff_levels > 0 %} import sys +from pathlib import Path -sys.path.insert(0, os.getcwd()) +sys.path.insert(0, str(Path(__file__) +{% for _ in range(diff_levels) %} +.parent +{% endfor %} +)) {% endif %} from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase {% for import_str in imports_list %} @@ -86,44 +91,45 @@ def __ensure_absolute(path: Text) -> Text: return absolute_path -def __convert_relative_current_working_dir(abs_path: Text) -> Text: - """ convert absolute path to relative path, based on os.getcwd() +def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: + """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space Args: - abs_path: absolute path + file_abs_path: absolute file path - Returns: relative path based on os.getcwd() + Returns: + ensured valid absolute file path """ - cwd = os.getcwd() - if not abs_path.startswith(cwd): - raise exceptions.ParamsError( - f"failed to convert absolute path to relative path based on os.getcwd()\n" - f"abs_path: {abs_path}\n" - f"os.getcwd(): {cwd}" - ) + project_meta = load_project_meta(file_abs_path) + raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path) + file_suffix = file_suffix.lower() - return abs_path[len(cwd) + 1 :] + raw_file_relative_name = convert_relative_project_root_dir(raw_abs_file_name) + if raw_file_relative_name == "": + return file_abs_path + path_names = [] + for name in raw_file_relative_name.rstrip(os.sep).split(os.sep): -def __convert_relative_project_root_dir(abs_path: Text) -> Text: - """ convert absolute path to relative path, based on project_meta.RootDir + if name[0] in string.digits: + # ensure file name not startswith digit + # 19 => T19, 2C => T2C + name = f"T{name}" - Args: - abs_path: absolute path + if name.startswith("."): + # avoid ".csv" been converted to "_csv" + pass + else: + # handle cases when directory name includes dot/hyphen/space + name = name.replace(" ", "_").replace(".", "_").replace("-", "_") - Returns: relative path based on project_meta.RootDir + path_names.append(name) - """ - project_meta = load_project_meta(abs_path) - if not abs_path.startswith(project_meta.RootDir): - raise exceptions.ParamsError( - f"failed to convert absolute path to relative path based on project_meta.RootDir\n" - f"abs_path: {abs_path}\n" - f"project_meta.RootDir: {project_meta.RootDir}" - ) - - return abs_path[len(project_meta.RootDir) + 1 :] + new_file_path = os.path.join( + project_meta.RootDir, f"{os.sep.join(path_names)}{file_suffix}" + ) + return new_file_path def __ensure_testcase_module(path: Text) -> NoReturn: @@ -137,31 +143,6 @@ def __ensure_testcase_module(path: Text) -> NoReturn: f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!\n") -def __ensure_project_meta_files(tests_path: Text) -> NoReturn: - """ ensure project meta files exist in generated pytest folder files - include debugtalk.py and .env - """ - project_meta = load_project_meta(tests_path) - - # handle cases when generated pytest directory are different from original yaml/json testcases - debugtalk_path = project_meta.debugtalk_path - if debugtalk_path: - debugtalk_new_path = ensure_file_abs_path_valid(debugtalk_path) - if debugtalk_new_path != debugtalk_path: - logger.info(f"copy debugtalk.py to {debugtalk_new_path}") - copyfile(debugtalk_path, debugtalk_new_path) - - global pytest_files_made_cache_mapping - pytest_files_made_cache_mapping[debugtalk_new_path] = "" - - dot_csv_path = project_meta.dot_env_path - if dot_csv_path: - dot_csv_new_path = ensure_file_abs_path_valid(dot_csv_path) - if dot_csv_new_path != dot_csv_path: - logger.info(f"copy .env to {dot_csv_new_path}") - copyfile(dot_csv_path, dot_csv_new_path) - - def convert_testcase_path(testcase_abs_path: Text) -> Tuple[Text, Text]: """convert single YAML/JSON testcase path to python file""" testcase_new_path = ensure_file_abs_path_valid(testcase_abs_path) @@ -358,7 +339,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: return testcase_python_abs_path config = testcase["config"] - config["path"] = __convert_relative_project_root_dir(testcase_python_abs_path) + config["path"] = convert_relative_project_root_dir(testcase_python_abs_path) config["variables"] = convert_variables( config.get("variables", {}), testcase_abs_path ) @@ -388,7 +369,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase - ref_testcase_python_relative_path = __convert_relative_current_working_dir( + ref_testcase_python_relative_path = convert_relative_project_root_dir( ref_testcase_python_abs_path ) ref_module_name, _ = os.path.splitext(ref_testcase_python_relative_path) @@ -397,9 +378,14 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" ) + testcase_path = convert_relative_project_root_dir(testcase_abs_path) + # current file compared to ProjectRootDir + diff_levels = len(testcase_path.split(os.sep)) + data = { "version": __version__, - "testcase_path": __convert_relative_project_root_dir(testcase_abs_path), + "testcase_path": testcase_path, + "diff_levels": diff_levels, "class_name": f"TestCase{testcase_cls_name}", "imports_list": imports_list, "config_chain_style": make_config_chain_style(config), @@ -564,8 +550,6 @@ def main_make(tests_paths: List[Text]) -> List[Text]: logger.error(ex) sys.exit(1) - __ensure_project_meta_files(tests_path) - # format pytest files pytest_files_format_list = pytest_files_made_cache_mapping.keys() format_pytest_with_black(*pytest_files_format_list) diff --git a/httprunner/utils.py b/httprunner/utils.py index 0b22e049..d832f881 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -3,16 +3,14 @@ import copy import json import os.path import platform -import string import uuid -from typing import Dict, List, Any, Text +from typing import Dict, List, Any import sentry_sdk -from loguru import logger - from httprunner import __version__ from httprunner import exceptions from httprunner.models import VariablesMapping +from loguru import logger def init_sentry_sdk(): @@ -181,45 +179,6 @@ def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List): ) -def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: - """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space - - Args: - file_abs_path: absolute file path - - Returns: - ensured valid absolute file path - - """ - raw_abs_file_name, file_suffix = os.path.splitext(file_abs_path) - file_suffix = file_suffix.lower() - - raw_file_relative_name = raw_abs_file_name[len(os.getcwd()) + 1 :] - - if raw_file_relative_name == "": - return file_abs_path - - path_names = [] - for name in raw_file_relative_name.rstrip(os.sep).split(os.sep): - - if name[0] in string.digits: - # ensure file name not startswith digit - # 19 => T19, 2C => T2C - name = f"T{name}" - - if name.startswith("."): - # avoid ".csv" been converted to "_csv" - pass - else: - # handle cases when directory name includes dot/hyphen/space - name = name.replace(" ", "_").replace(".", "_").replace("-", "_") - - path_names.append(name) - - new_file_path = os.path.join(os.getcwd(), f"{os.sep.join(path_names)}{file_suffix}") - return new_file_path - - class ExtendJSONEncoder(json.JSONEncoder): """ especially used to safely dump json data with python object, such as MultipartEncoder """ diff --git a/tests/cli_test.py b/tests/cli_test.py index c84e294b..863ef0bd 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,4 +1,5 @@ import io +import os import sys import unittest @@ -40,10 +41,12 @@ class TestCli(unittest.TestCase): self.assertIn(__description__, self.captured_output.getvalue().strip()) def test_debug_pytest(self): - exit_code = pytest.main( - [ - "-s", - "examples/postman_echo/request_methods/request_with_testcase_reference_test.py", - ] - ) - self.assertEqual(exit_code, 0) + cwd = os.getcwd() + try: + os.chdir(os.path.join(cwd, "examples", "postman_echo")) + exit_code = pytest.main( + ["-s", "request_methods/request_with_testcase_reference_test.py",] + ) + self.assertEqual(exit_code, 0) + finally: + os.chdir(cwd) diff --git a/tests/data/a-b.c/__init__.py b/tests/data/.csv similarity index 100% rename from tests/data/a-b.c/__init__.py rename to tests/data/.csv diff --git a/tests/data/a-b.c/2 3.yml b/tests/data/a-b.c/2 3.yml index f20d5b4d..8a37b3a8 100644 --- a/tests/data/a-b.c/2 3.yml +++ b/tests/data/a-b.c/2 3.yml @@ -6,7 +6,7 @@ config: teststeps: - name: request with functions - testcase: 1.yml + testcase: a-b.c/1.yml export: - session_foo2 - diff --git a/tests/data/a-b.c/中文case.yml b/tests/data/a-b.c/中文case.yml new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/a-b.c/debugtalk.py b/tests/data/debugtalk.py similarity index 100% rename from tests/data/a-b.c/debugtalk.py rename to tests/data/debugtalk.py diff --git a/tests/make_test.py b/tests/make_test.py index b7f1ec68..f5aeb6ae 100644 --- a/tests/make_test.py +++ b/tests/make_test.py @@ -8,6 +8,7 @@ from httprunner.make import ( make_config_chain_style, make_teststep_chain_style, pytest_files_run_set, + ensure_file_abs_path_valid, ) from httprunner import loader @@ -64,7 +65,7 @@ class TestMake(unittest.TestCase): content = f.read() self.assertIn( """ -from examples.postman_echo.request_methods.request_with_functions_test import ( +from request_methods.request_with_functions_test import ( TestCaseRequestWithFunctions as RequestWithFunctions, ) """, @@ -90,53 +91,57 @@ from examples.postman_echo.request_methods.request_with_functions_test import ( testcase_python_list, ) + def test_ensure_file_path_valid(self): + self.assertEqual( + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "tests", "data", "a-b.c", "2 3.yml") + ), + os.path.join(os.getcwd(), "tests", "data", "a_b_c", "T2_3.yml"), + ) + loader.project_meta = None + self.assertEqual( + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods") + ), + os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods"), + ) + loader.project_meta = None + self.assertEqual( + ensure_file_abs_path_valid(os.path.join(os.getcwd(), "README.md")), + os.path.join(os.getcwd(), "README.md"), + ) + loader.project_meta = None + self.assertEqual( + ensure_file_abs_path_valid(os.getcwd()), os.getcwd(), + ) + loader.project_meta = None + self.assertEqual( + ensure_file_abs_path_valid( + os.path.join(os.getcwd(), "tests", "data", ".csv") + ), + os.path.join(os.getcwd(), "tests", "data", ".csv"), + ) + def test_convert_testcase_path(self): - self.assertEqual( - convert_testcase_path(os.path.join(os.getcwd(), "mubu.login.yml")), - (os.path.join(os.getcwd(), "mubu_login_test.py"), "MubuLogin"), - ) self.assertEqual( convert_testcase_path( - os.path.join(os.getcwd(), os.path.join("path", "to", "mubu.login.yml")) + os.path.join(os.getcwd(), "tests", "data", "a-b.c", "2 3.yml") ), ( - os.path.join( - os.getcwd(), os.path.join("path", "to", "mubu_login_test.py") - ), - "MubuLogin", + os.path.join(os.getcwd(), "tests", "data", "a_b_c", "T2_3_test.py"), + "T23", ), ) self.assertEqual( convert_testcase_path( - os.path.join(os.getcwd(), "path", "to 2", "mubu.login.yml") + os.path.join(os.getcwd(), "tests", "data", "a-b.c", "中文case.yml") ), ( os.path.join( - os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py") + os.getcwd(), + os.path.join("tests", "data", "a_b_c", "中文case_test.py"), ), - "MubuLogin", - ), - ) - self.assertEqual( - convert_testcase_path( - os.path.join(os.getcwd(), "path", "to-2", "mubu login.yml") - ), - ( - os.path.join( - os.getcwd(), os.path.join("path", "to_2", "mubu_login_test.py") - ), - "MubuLogin", - ), - ) - self.assertEqual( - convert_testcase_path( - os.path.join(os.getcwd(), "path", "to.2", "幕布login.yml") - ), - ( - os.path.join( - os.getcwd(), os.path.join("path", "to_2", "幕布login_test.py") - ), - "幕布Login", + "中文Case", ), ) diff --git a/tests/runner_test.py b/tests/runner_test.py index b91fd3df..96654d3b 100644 --- a/tests/runner_test.py +++ b/tests/runner_test.py @@ -35,6 +35,6 @@ class TestHttpRunner(unittest.TestCase): exit_code = main_run(["tests/data/a-b.c/2 3.yml"]) self.assertEqual(exit_code, 0) self.assertTrue(os.path.exists("tests/data/a_b_c/__init__.py")) - self.assertTrue(os.path.exists("tests/data/a_b_c/debugtalk.py")) + self.assertTrue(os.path.exists("tests/data/debugtalk.py")) self.assertTrue(os.path.exists("tests/data/a_b_c/T1_test.py")) self.assertTrue(os.path.exists("tests/data/a_b_c/T2_3_test.py")) diff --git a/tests/utils_test.py b/tests/utils_test.py index bd4e3f53..159ea4e1 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -5,7 +5,6 @@ import unittest from httprunner import loader, utils from httprunner.utils import ( - ensure_file_abs_path_valid, ExtendJSONEncoder, override_config_variables, ) @@ -105,41 +104,6 @@ class TestUtils(unittest.TestCase): ["A", "D", "C", "B"], ) - def test_ensure_file_path_valid(self): - self.assertEqual( - ensure_file_abs_path_valid( - os.path.join(os.getcwd(), "examples", "a-b.c", "d f", "hardcode.yml") - ), - os.path.join(os.getcwd(), "examples", "a_b_c", "d_f", "hardcode.yml"), - ) - self.assertEqual( - ensure_file_abs_path_valid(os.path.join(os.getcwd(), "1", "2B", "3.yml")), - os.path.join(os.getcwd(), "T1", "T2B", "T3.yml"), - ) - self.assertEqual( - ensure_file_abs_path_valid( - os.path.join(os.getcwd(), "examples", "a-b.c", "2B", "hardcode.yml") - ), - os.path.join(os.getcwd(), "examples", "a_b_c", "T2B", "hardcode.yml"), - ) - self.assertEqual( - ensure_file_abs_path_valid( - os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods") - ), - os.path.join(os.getcwd(), "examples", "postman_echo", "request_methods"), - ) - self.assertEqual( - ensure_file_abs_path_valid(os.path.join(os.getcwd(), "test.yml")), - os.path.join(os.getcwd(), "test.yml"), - ) - self.assertEqual( - ensure_file_abs_path_valid(os.getcwd()), os.getcwd(), - ) - self.assertEqual( - ensure_file_abs_path_valid(os.path.join(os.getcwd(), "demo", ".csv")), - os.path.join(os.getcwd(), "demo", ".csv"), - ) - def test_safe_dump_json(self): class A(object): pass From 5be6c781ee8a1870d86ea0426c6a8c48dc64b4f4 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 21 Jun 2020 11:23:01 +0800 Subject: [PATCH 22/26] change: log function details when call failed --- httprunner/parser.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/httprunner/parser.py b/httprunner/parser.py index 00ca4300..32a17192 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -3,6 +3,7 @@ import builtins import re from typing import Any, Set, Text, Callable, List, Dict +from loguru import logger from sentry_sdk import capture_exception from httprunner import loader, utils, exceptions @@ -331,10 +332,20 @@ def parse_string( function_meta = parse_function_params(func_params_str) args = function_meta["args"] kwargs = function_meta["kwargs"] - parsed_args = parse_data(args, variables_mapping, functions_mapping) parsed_kwargs = parse_data(kwargs, variables_mapping, functions_mapping) - func_eval_value = func(*parsed_args, **parsed_kwargs) + + try: + func_eval_value = func(*parsed_args, **parsed_kwargs) + except Exception as ex: + logger.error( + f"call function error:\n" + f"func_name: {func_name}\n" + f"args: {parsed_args}\n" + f"kwargs: {parsed_kwargs}\n" + f"{type(ex).__name__}: {ex}" + ) + raise func_raw_str = "${" + func_name + f"({func_params_str})" + "}" if func_raw_str == raw_string: From a2fc817b5e9405cc959a6e24d747f266adb3f3a7 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 21 Jun 2020 11:46:45 +0800 Subject: [PATCH 23/26] change: use private method --- httprunner/compat.py | 54 ++++++++++++++++++++++++++------------------ tests/compat_test.py | 43 +++++++++++++++++------------------ 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/httprunner/compat.py b/httprunner/compat.py index 17766ed9..f7e6275b 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -5,11 +5,12 @@ import os import sys from typing import List, Dict, Text, Union, Any +from loguru import logger + from httprunner import exceptions from httprunner.loader import load_project_meta, convert_relative_project_root_dir from httprunner.parser import parse_data from httprunner.utils import sort_dict_by_custom_order -from loguru import logger def convert_variables( @@ -45,7 +46,7 @@ def convert_variables( ) -def convert_jmespath(raw: Text) -> Text: +def _convert_jmespath(raw: Text) -> Text: if not isinstance(raw, Text): raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}") @@ -77,7 +78,7 @@ def convert_jmespath(raw: Text) -> Text: return ".".join(raw_list) -def convert_extractors(extractors: Union[List, Dict]) -> Dict: +def _convert_extractors(extractors: Union[List, Dict]) -> Dict: """ convert extract list(v2) to dict(v3) Args: @@ -105,26 +106,26 @@ def convert_extractors(extractors: Union[List, Dict]) -> Dict: sys.exit(1) for k, v in v3_extractors.items(): - v3_extractors[k] = convert_jmespath(v) + v3_extractors[k] = _convert_jmespath(v) return v3_extractors -def convert_validators(validators: List) -> List: +def _convert_validators(validators: List) -> List: for v in validators: if "check" in v and "expect" in v: # format1: {"check": "content.abc", "assert": "eq", "expect": 201} - v["check"] = convert_jmespath(v["check"]) + v["check"] = _convert_jmespath(v["check"]) elif len(v) == 1: # format2: {'eq': ['status_code', 201]} comparator = list(v.keys())[0] - v[comparator][0] = convert_jmespath(v[comparator][0]) + v[comparator][0] = _convert_jmespath(v[comparator][0]) return validators -def sort_request_by_custom_order(request: Dict) -> Dict: +def _sort_request_by_custom_order(request: Dict) -> Dict: custom_order = [ "method", "url", @@ -145,7 +146,7 @@ def sort_request_by_custom_order(request: Dict) -> Dict: return sort_dict_by_custom_order(request, custom_order) -def sort_step_by_custom_order(step: Dict) -> Dict: +def _sort_step_by_custom_order(step: Dict) -> Dict: custom_order = [ "name", "variables", @@ -160,7 +161,7 @@ def sort_step_by_custom_order(step: Dict) -> Dict: return sort_dict_by_custom_order(step, custom_order) -def ensure_step_attachment(step: Dict) -> Dict: +def _ensure_step_attachment(step: Dict) -> Dict: test_dict = { "name": step["name"], } @@ -175,13 +176,13 @@ def ensure_step_attachment(step: Dict) -> Dict: test_dict["teardown_hooks"] = step["teardown_hooks"] if "extract" in step: - test_dict["extract"] = convert_extractors(step["extract"]) + test_dict["extract"] = _convert_extractors(step["extract"]) if "export" in step: test_dict["export"] = step["export"] if "validate" in step: - test_dict["validate"] = convert_validators(step["validate"]) + test_dict["validate"] = _convert_validators(step["validate"]) if "validate_script" in step: test_dict["validate_script"] = step["validate_script"] @@ -190,12 +191,14 @@ def ensure_step_attachment(step: Dict) -> Dict: def ensure_testcase_v3_api(api_content: Dict) -> Dict: - teststep = { - "request": api_content["request"], - } - teststep.update(ensure_step_attachment(api_content)) + logger.info("convert api in v2 to testcase format v3") - teststep = sort_step_by_custom_order(teststep) + teststep = { + "request": _sort_request_by_custom_order(api_content["request"]), + } + teststep.update(_ensure_step_attachment(api_content)) + + teststep = _sort_step_by_custom_order(teststep) return { "config": {"name": api_content["name"]}, @@ -204,6 +207,8 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict: def ensure_testcase_v3(test_content: Dict) -> Dict: + logger.info("ensure compatibility with testcase format v2") + v3_content = {"config": test_content["config"], "teststeps": []} if "teststeps" not in test_content: @@ -220,7 +225,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict: teststep = {} if "request" in step: - teststep["request"] = step.pop("request") + teststep["request"] = _sort_request_by_custom_order(step.pop("request")) elif "api" in step: teststep["testcase"] = step.pop("api") elif "testcase" in step: @@ -228,9 +233,9 @@ def ensure_testcase_v3(test_content: Dict) -> Dict: else: raise exceptions.TestCaseFormatError(f"Invalid teststep: {step}") - teststep.update(ensure_step_attachment(step)) + teststep.update(_ensure_step_attachment(step)) - teststep = sort_step_by_custom_order(teststep) + teststep = _sort_step_by_custom_order(teststep) v3_content["teststeps"].append(teststep) return v3_content @@ -241,23 +246,28 @@ def ensure_cli_args(args: List) -> List: """ # remove deprecated --failfast if "--failfast" in args: + logger.warning(f"remove deprecated argument: --failfast") args.pop(args.index("--failfast")) # convert --report-file to --html if "--report-file" in args: + logger.warning(f"replace deprecated argument --report-file with --html") index = args.index("--report-file") args[index] = "--html" args.append("--self-contained-html") # keep compatibility with --save-tests in v2 if "--save-tests" in args: + logger.warning( + f"generate conftest.py keep compatibility with --save-tests in v2" + ) args.pop(args.index("--save-tests")) - generate_conftest_for_summary(args) + _generate_conftest_for_summary(args) return args -def generate_conftest_for_summary(args: List): +def _generate_conftest_for_summary(args: List): for arg in args: if os.path.exists(arg): diff --git a/tests/compat_test.py b/tests/compat_test.py index b5d45250..0b5f3bc3 100644 --- a/tests/compat_test.py +++ b/tests/compat_test.py @@ -2,7 +2,6 @@ import os import unittest from httprunner import compat, exceptions, loader -from httprunner.compat import convert_variables, ensure_path_sep class TestCompat(unittest.TestCase): @@ -12,72 +11,72 @@ class TestCompat(unittest.TestCase): def test_convert_variables(self): raw_variables = [{"var1": 1}, {"var2": "val2"}] self.assertEqual( - convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + compat.convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), {"var1": 1, "var2": "val2"}, ) raw_variables = {"var1": 1, "var2": "val2"} self.assertEqual( - convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + compat.convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), {"var1": 1, "var2": "val2"}, ) raw_variables = "${get_variables()}" self.assertEqual( - convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), + compat.convert_variables(raw_variables, "tests/data/a-b.c/1.yml"), {"foo1": "session_bar1"}, ) with self.assertRaises(exceptions.TestCaseFormatError): raw_variables = [{"var1": 1}, {"var2": "val2", "var3": 3}] - convert_variables(raw_variables, "tests/data/a-b.c/1.yml") + compat.convert_variables(raw_variables, "tests/data/a-b.c/1.yml") with self.assertRaises(exceptions.TestCaseFormatError): - convert_variables(None, "tests/data/a-b.c/1.yml") + compat.convert_variables(None, "tests/data/a-b.c/1.yml") def test_convert_jmespath(self): - self.assertEqual(compat.convert_jmespath("content.abc"), "body.abc") - self.assertEqual(compat.convert_jmespath("json.abc"), "body.abc") + self.assertEqual(compat._convert_jmespath("content.abc"), "body.abc") + self.assertEqual(compat._convert_jmespath("json.abc"), "body.abc") self.assertEqual( - compat.convert_jmespath("headers.Content-Type"), 'headers."Content-Type"' + compat._convert_jmespath("headers.Content-Type"), 'headers."Content-Type"' ) self.assertEqual( - compat.convert_jmespath('headers."Content-Type"'), 'headers."Content-Type"' + compat._convert_jmespath('headers."Content-Type"'), 'headers."Content-Type"' ) self.assertEqual( - compat.convert_jmespath("body.data.buildings.0.building_id"), + compat._convert_jmespath("body.data.buildings.0.building_id"), "body.data.buildings[0].building_id", ) with self.assertRaises(SystemExit): - compat.convert_jmespath("2.buildings.0.building_id") + compat._convert_jmespath("2.buildings.0.building_id") def test_convert_extractors(self): self.assertEqual( - compat.convert_extractors( + compat._convert_extractors( [{"varA": "content.varA"}, {"varB": "json.varB"}] ), {"varA": "body.varA", "varB": "body.varB"}, ) self.assertEqual( - compat.convert_extractors([{"varA": "content.0.varA"}]), + compat._convert_extractors([{"varA": "content.0.varA"}]), {"varA": "body[0].varA"}, ) self.assertEqual( - compat.convert_extractors({"varA": "content.0.varA"}), + compat._convert_extractors({"varA": "content.0.varA"}), {"varA": "body[0].varA"}, ) def test_convert_validators(self): self.assertEqual( - compat.convert_validators( + compat._convert_validators( [{"check": "content.abc", "assert": "eq", "expect": 201}] ), [{"check": "body.abc", "assert": "eq", "expect": 201}], ) self.assertEqual( - compat.convert_validators([{"eq": ["content.abc", 201]}]), + compat._convert_validators([{"eq": ["content.abc", 201]}]), [{"eq": ["body.abc", 201]}], ) self.assertEqual( - compat.convert_validators([{"eq": ["content.0.name", 201]}]), + compat._convert_validators([{"eq": ["content.0.name", 201]}]), [{"eq": ["body[0].name", 201]}], ) @@ -216,16 +215,16 @@ class TestCompat(unittest.TestCase): def test_ensure_file_path(self): self.assertEqual( - ensure_path_sep("demo\\test.yml"), os.sep.join(["demo", "test.yml"]) + compat.ensure_path_sep("demo\\test.yml"), os.sep.join(["demo", "test.yml"]) ) self.assertEqual( - ensure_path_sep(os.path.join(os.getcwd(), "demo\\test.yml")), + compat.ensure_path_sep(os.path.join(os.getcwd(), "demo\\test.yml")), os.path.join(os.getcwd(), os.sep.join(["demo", "test.yml"])), ) self.assertEqual( - ensure_path_sep("demo/test.yml"), os.sep.join(["demo", "test.yml"]) + compat.ensure_path_sep("demo/test.yml"), os.sep.join(["demo", "test.yml"]) ) self.assertEqual( - ensure_path_sep(os.path.join(os.getcwd(), "demo/test.yml")), + compat.ensure_path_sep(os.path.join(os.getcwd(), "demo/test.yml")), os.path.join(os.getcwd(), os.sep.join(["demo", "test.yml"])), ) From d58d45a1963eff9b0fc1635b2133c7b9e5e931d3 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 21 Jun 2020 11:54:14 +0800 Subject: [PATCH 24/26] fix: check config type --- httprunner/make.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/httprunner/make.py b/httprunner/make.py index b7a908d2..ec07bed2 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -511,6 +511,11 @@ def __make(tests_path: Text) -> NoReturn: f"Invalid testcase/testsuite: missing config part in testcase/testsuite.\npath: {test_file}" ) continue + elif not isinstance(test_content["config"], Dict): + logger.warning( + f"Invalid testcase/testsuite: config should be dict type, got {test_content['config']}" + ) + continue # ensure path absolute test_content.setdefault("config", {})["path"] = test_file From 3841406dd9d97774b40c85189bf68df282278ca0 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 21 Jun 2020 12:25:39 +0800 Subject: [PATCH 25/26] change: add logs --- httprunner/loader.py | 2 -- httprunner/make.py | 29 ++++++++++++++++++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/httprunner/loader.py b/httprunner/loader.py index 1a9e6484..429b4d07 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -78,7 +78,6 @@ def load_testcase(testcase: Dict) -> TestCase: testcase_obj = TestCase.parse_obj(testcase) except ValidationError as ex: err_msg = f"TestCase ValidationError:\nerror: {ex}\ncontent: {testcase}" - logger.error(err_msg) raise exceptions.TestCaseFormatError(err_msg) return testcase_obj @@ -99,7 +98,6 @@ def load_testsuite(testsuite: Dict) -> TestSuite: testsuite_obj = TestSuite.parse_obj(testsuite) except ValidationError as ex: err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}" - logger.error(err_msg) raise exceptions.TestSuiteFormatError(err_msg) return testsuite_obj diff --git a/httprunner/make.py b/httprunner/make.py index ec07bed2..9053c0c8 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -478,6 +478,7 @@ def __make(tests_path: Text) -> NoReturn: tests_path: should be in absolute path """ + logger.info(f"make path: {tests_path}") test_files = [] if os.path.isdir(tests_path): files_list = load_folder_files(tests_path) @@ -495,11 +496,14 @@ def __make(tests_path: Text) -> NoReturn: try: test_content = load_test_file(test_file) except (exceptions.FileNotFound, exceptions.FileFormatError) as ex: - logger.warning(ex) + logger.warning(f"Invalid test file: {test_file}\n{type(ex).__name__}: {ex}") continue if not isinstance(test_content, Dict): - logger.warning(f"test content not in dict format. \npath: {test_file}") + logger.warning( + f"Invalid test file: {test_file}\n" + f"reason: test content not in dict format." + ) continue # api in v2 format, convert to v3 testcase @@ -508,12 +512,14 @@ def __make(tests_path: Text) -> NoReturn: if "config" not in test_content: logger.warning( - f"Invalid testcase/testsuite: missing config part in testcase/testsuite.\npath: {test_file}" + f"Invalid testcase/testsuite file: {test_file}\n" + f"reason: missing config part." ) continue elif not isinstance(test_content["config"], Dict): logger.warning( - f"Invalid testcase/testsuite: config should be dict type, got {test_content['config']}" + f"Invalid testcase/testsuite file: {test_file}\n" + f"reason: config should be dict type, got {test_content['config']}" ) continue @@ -525,19 +531,28 @@ def __make(tests_path: Text) -> NoReturn: try: testcase_pytest_path = make_testcase(test_content) pytest_files_run_set.add(testcase_pytest_path) - except exceptions.TestCaseFormatError: + except exceptions.TestCaseFormatError as ex: + logger.warning( + f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}" + ) continue # testsuite elif "testcases" in test_content: try: make_testsuite(test_content) - except exceptions.TestSuiteFormatError: + except exceptions.TestSuiteFormatError as ex: + logger.warning( + f"Invalid testsuite file: {test_file}\n{type(ex).__name__}: {ex}" + ) continue # invalid format else: - logger.warning(f"skip invalid testcase/testsuite file: {test_file}") + logger.warning( + f"Invalid test file: {test_file}\n" + f"reason: file content is neither testcase nor testsuite" + ) def main_make(tests_paths: List[Text]) -> List[Text]: From 18a97964ac0899c185bfffa584acc26813f075ea Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 21 Jun 2020 12:34:35 +0800 Subject: [PATCH 26/26] docs: update changelog --- docs/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f82a34e1..cabaac4b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 3.1.0 (2020-06-19) +## 3.1.0 (2020-06-21) **Added** @@ -9,10 +9,11 @@ **Changed** - change: make converted referenced pytest files always relative to ProjectRootDir +- change: log function details when call function failed +- change: do not raise error if failed to get client/server address info **Fixed** -- change: do not raise error if failed to get client/server address info - fix: path handling error when har2case har file and cwd != ProjectRootDir - fix: missing list type for request body