{ "info": { "author": "idle-man", "author_email": "i@idleman.club", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: Other Audience", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: Unix", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Testing" ], "description": "# Parrot\n\n**Automated test solution for http requests based on recording and playback**\n\n## 1. Instructions for use\n\n### 1.1 Install\n\nThe Parrot project is developed based on Python 3\uff0c and the recommended version is 3.7.x. Please ensure that the corresponding version of python environment is installed on the target machine.\n\n**Method One: install via `pip` command**\n\niParrot project has been submitted to PyPI.\n\n1. You could install via `pip install iParrot` command.\n2. If you need a upgrade, try `pip install -U iParrot` command.\n\n**Method Two: install using source code**\n\nCode Repository: \n\nDownload source code pkg and install via `python setup.py install` command.\n\nAfter the installation is complete, the `parrot` executable will be generated, try `parrot help` or `parrot -v`. If there is a problem, feedback an issue: \n\n***\n\n### 1.2 Usage\n\n#### 1.2.1 View commands supported by Parrot: `parrot help`\n\nTwo core commands are: **record** and **playback**\n\n```\n$ parrot help\nAutomated test solution for http requests based on recording and playback\nVersion: ...\n\nUsage: parrot [-h] [-v] [command] []\n\ncommand:\n record - parse source file and generate test cases\n see detail usage: `parrot help record`\n playback - run standardized test cases and do validations\n see detail usage: `parrot help playback`\n template - generate standardized test case template file\n see detail usage: `parrot help template`\n replace - replace existing test cases with specified rules\n see detail usage: `parrot help replace`\n home - show homepage on github\n doc - show readme on github\n\n\noptional arguments:\n -h, --help show this help message and exit\n -v, -V, --version show version\n```\n\n#### 1.2.2 View usage of `record`: `parrot help record`\n\nThe purpose of this step is to parse the user-specified source file (currently .har) into a standardized set of use cases.\n\n```\n$ parrot help record\n...\n\nUsage: parrot record []\n\nArguments:\n -s, --source SOURCE source file with path, *.har or directory [required]\n -t, --target TARGET target output path, 'ParrotProject' as default\n -i, --include INCLUDE include filter on url, separated by ',' if multiple\n -e, --exclude EXCLUDE exclude filter on url, separated by ',' if multiple\n -vi, --validation-include V_INCLUDE\n include filter on response validation, separated by ',' if multiple\n -ve, --validation-exclude V_EXCLUDE \n exclude filter on response validation, separated by ',' if multiple\n -ae, --auto-extract automatic identification of interface dependencies or not, False as default\n\n --log-level LOG_LEVEL log level: debug, info, warn, error, info as default\n --log-mode LOG_MODE log mode : 1-on screen, 2-in log file, 3-1&2, 1 as default\n --log-path LOG_PATH log path : as default\n --log-name LOG_NAME log name : parrot.log as default\n\n```\n\n#### 1.2.3 View usage of `playback`: `parrot help playback`\n\nThis step is to execute the specified set of test cases and generate a test report.\n\n```\n$ parrot help playback\n...\n\nUsage: parrot playback []\n\nArguments:\n -s, --suite, -c, --case SUITE_OR_CASE\n test suite or case with path, *.yml or directory [required]\n -t, --target TARGET output path for report and log, 'ParrotProject' as default\n -i, --interval INTERVAL\n interval time(ms) between each step, use the recorded interval as default\n -env, --environment ENVIRONMENT\n environment tag, defined in project/environments/*.yml, use defined config in test_suites as default\n -reset, --reset-after-case\n reset runtime environment or not, False as default\n\n --fail-stop stop or not when a test step failed on validation, False as default\n --fail-retry-times FAIL_RETRY_TIMES\n max retry times when a test step failed on validation, 0 as default\n --fail-retry-interval FAIL_RETRY_INTERVAL \n retry interval(ms) when a test step failed on validation, 100 as default\n\n --log-level LOG_LEVEL log level: debug, info, warn, error, info as default\n --log-mode LOG_MODE log mode : 1-on screen, 2-in log file, 3-1&2, 1 as default\n --log-path LOG_PATH log path : as default\n --log-name LOG_NAME log name : parrot.log as default\n\n```\n\n#### 1.2.4 View usage of `template`: `parrot help template`\n\nThis step is to automatically generate standardized test case templates and examples, which is convenient for users to refer to self-built use cases.\n\n```\n$ parrot help template\n...\n\nUsage: parrot template []\n\nArguments:\n -t, --target TARGET target output path, 'ParrotProject' as default\n\n --log-level LOG_LEVEL log level: debug, info, warn, error, info as default\n --log-mode LOG_MODE log mode : 1-on screen, 2-in log file, 3-1&2, 1 as default\n --log-path LOG_PATH log path : as default\n --log-name LOG_NAME log name : parrot.log as default\n\n```\n\n#### 1.2.5 View usage of `replace`: `parrot help replace`\n\nThis step is to batch replace the `config.variables` of the generated use cases according to your specified rules.\n\n```\n$ parrot help replace\n...\n\nUsage: parrot replace []\n\nArguments:\n -s, --suite, -c, --case SUITE_OR_CASE\n test suite or case with path, *.yml or directory [required]\n -t, --target TARGET target output path, 'ParrotProjectNew' as default\n -r, --rule RULE replace rules, separated by ',' if multiple [required]\n 'key=>value', 'value1=>value2' or 'apiA::key=>value' only for specified api\n\n --log-level LOG_LEVEL log level: debug, info, warn, error, info as default\n --log-mode LOG_MODE log mode : 1-on screen, 2-in log file, 3-1&2, 1 as default\n --log-path LOG_PATH log path : as default\n --log-name LOG_NAME log name : parrot.log as default\n\n```\n\n***\n\n### 1.3 Demo\n\n#### 1.3.1 Build Sample application and export HAR\n\nIn order to facilitate the function debugging and operation demonstration, a simple web application(based on Python Flask) was specially built: \n\nPlease refer to its README to complete the application setup in the local environment. It provides the \"recommended function operations\".\n\nWe use this as an example, using the browser's developer tools, after completing this series of operations, export the HAR file, assuming we named it `sample.har` and placed it in your working directory.\n\n> HAR is a common standardized format for storing HTTP requests and responses.\n> \n> Its versatility: can be exported with consistent format from Charles, Fiddler, Chrome, etc.\n> \n> Its standardization: JSON format and UTF-8 coding.\n\n***\n\n#### 1.3.2 Record - Transforming HAR into standardized use cases\n\nWe assume that you have completed the installation of `parrot` as described in Chapter 1. Now we use the command line tool on the computer and switch to the path where `sample.har` is located.\n\n> It is recommended to use `PyCharm`, which contains `Terminal`, which is convenient for operation and helps to view the following use cases.\n\nAccording to the instructions in Section 1.2.2, we have a general understanding of the basic usage of the `record` command. Now let's make a try on `sample.har`.\n\n**# A simple record: -s & -t**\n\n```\n$ parrot record -s sample.har -t demo0\n```\n\nIf it is successful, after the execution is complete, you will see the generated use cases set in current directory. The structure is as follows:\n\n```\ndemo0/\n \u251c\u2500\u2500 environments\n \u2502\u00a0\u00a0 \u2514\u2500\u2500 *env*.yml: Project-level environment variable configuration\n \u251c\u2500\u2500 test_steps\n \u2502\u00a0\u00a0 \u2514\u2500\u2500 *case_name*.yml: The minimum execution unit, a http request is a step\n \u251c\u2500\u2500 test_cases\n \u2502\u00a0\u00a0 \u2514\u2500\u2500 *case_name*.yml: Independent closed-loop unit, consisting of one or more steps\n \u2514\u2500\u2500 test_suites\n \u00a0\u00a0 \u2514\u2500\u2500 *suite_name*.yml: Test case set, consisting of one or more cases\n```\n\nEach `Entry` in HAR will be converted into a test_step containing the specific request and validation information.\n\nEach `test_step` `test_case` `test_suite` contains its own `config` `setup_hooks` `teardown_hooks`.\n\nThe specific format can be found in the `Appendix: Use Case Structure`.\n\n**# Not all recorded requests need to be converted to use cases: -i & -e**\n\nBecause HAR is a full export without selection, it may contain some requests that we don't need to test, such as `*.css` `*.js`. In order to avoid the manual processing, we can filter them out during the record phase.\n\nParrot currently provides -i/--include and -e/--exclude filter modes, specifically:\n\n* -i, --include: According to the specified parameters, the `url` in each `Entry` is ambiguously matched, and the matching will be retained. The relationship between multiple include parameters is OR.\n* -e, --exclude: According to the specified parameters, the `url` in each `Entry` is ambiguously matched, and the matching will be excluded. The relationship between multiple excludes is OR.\n\nThe two parameters can be used only in one or in combination to achieve your filtering needs.\n\nIn our above requirement, we want to filter out the unwanted `*.css` and `*.js`, just use -e, --exclude.\n\n```\n$ parrot record -s sample.har -t demo1 -e \".css, .js\"\n```\n\nAfter that, you can check if the requirement is met in demo1/test_steps.\n\n**# Not all content in response can be expected: -vi & -ve**\n\nAn example of the validations generated in test_step, the user can also customize this format:\n\n```\nvalidations:\n- :\n : \n```\n\nThe `check`: According to the important information in the above Mode One, the unified format is: \\.\\\n\n- Available PREFIX: `status`, `content`, `headers`, `cookies`, in lower case\n- KEYS in `status`: `code`\n- KEYS in `headers` and `cookies`: Currently only extracting the outer keys\n- KEYS in `content`(json format): `content.a.b[1].c`\n\n**By default, automatically generated validations will include `status.code` and all keys of `content` and unofficial keys of `headers`.**\n\nIn fact, some of these keys do not need to be verified for reasons such as variability, etc., such as the `tag` `timestamp` contained in the response information of each request in `sample.har`, which is best removed when it is automatically generated.\n\nParrot currently provides -vi/--validation-include and -ve/--validation-exclude filter modes, specifically:\n\n* -vi, --validation-include: According to the specified parameters, the `headers` and `content` in the response in each Entry are fuzzy matched, and the matching will be retained. The relationship between multiple -vi is OR.\n* -ve, --validation-exclude: According to the specified parameters, the `headers` and `content` in the response in each Entry are fuzzy matched, the matching will be excluded. The relationship between multiple -ve is OR.\n\nThe two parameters can be used only in one or in combination to achieve your filtering needs.\n\nIn our above requirement, we want to filter out unwanted `tag` and `timestamp`, just use -ve, --validation-exclude.\n\n```\n$ parrot record -s sample.har -t demo2 -e \".css, .js\" -ve \"content.tag, timestamp\"\n```\n\nAbout the `comparator`, the default is `eq` when it is automatically generated, which can be edited manually. Currently, to view all the comparators that parrot supports, see `Appendix: Verification Method Set`.\n\n**# Sometimes we need to generate the parameters in real time.**\n\nTake the `hobby_suggest` request in `sample.har` as an example. The `today` parameter of the interface needs the date of the execution time. The default value recorded by the recording is the static value. If we run it in the next day, it will not meet the requirements. At this point, you need to generate this parameter in real time.\n\nThe way Parrot supports is: `${{function(params)}}`, where those functions are provided in `iparrot.modules.helper`, which can be used directly. See `Appendix: Helper Method Set`.\n\nExample:\n\n```yaml\nconfig:\n ...\n variables:\n p1: ABC\n p2: 123\n p3: ${{today()}}\nrequest:\n method: GET\n ...\n params:\n param1: ${p1}\n param2: ${p2}\n today: ${p3}\n ...\n```\n\n**# Sometimes there is a dependency on the response of the previous request.**\n\nTake the `hobby_detail` request in `sample.har` as an example. Its `name` argument is from the real-time valid return in the `hobby_list` request's response. If the data during recording is directly played back, it is likely that there is a failure sometimes.\n\nParrot's solution is: `extract` the specific key in the response of the `hobby_list` request, and use the `${variable}` format in the `hobby_detail` request.\n\nExample:\n\nhobby_list:\n\n```yaml\nconfig:\n ...\nrequest:\n ...\nresponse:\n extract:\n hobby: content.hobbies[0].name\n...\n```\n\nhobby_detail:\n\n```yaml\nconfig:\n ...\nrequest:\n ...\n params:\n name: ${hobby}\n ...\n```\n\nIn iParrot 1.0.6 and later, `parrot record` has the `-ae, --auto-extract` parameter added. \n\nIf this parameter is specified, parrot will automatically recognize the parameter dependency between interfaces during the parsing process.\n\nThe `extract` extraction and `${variable}` references are automatically completed when the use cases are generated.\n\nIn view of the possibility of many formats in the actual scene, the 'automatic' identification may cause some accidental or omission, and it is recommended to perform artificial inspection and correction after its execution.\n\nRegarding the recording phase, the above scenarios should cover most of the use cases. If there are other unsupported questions, welcome feedback an issue: \n\n***\n\n#### 1.3.3 Template - Generate a standardized use case template when no files are recorded\n\nIn actual work, there may be cases where there is no HAR file, such as a new project or a new interface before going online. \n\nFor users who have existing HAR files, you can skip this step.\n\n`parrot`1.1.0 and later provides the `template` command to help you generate standardized use case templates, where you can manually edit your test cases.\n\nSee section #1.2.4 for specific usage: `parrot help template`\n\nThe generated use case structure is consistent with the `recording` phase, including a `test_suite`, a `test_case`, a `test_step` and a `environment`.\n\nUsers can refer above template to write their own formatting use cases, which can also be used for subsequent `playback`.\n\n***\n\n#### 1.3.4 Replace - Batch update generated use cases according to specified rules\n\nFor use cases that have already been generated, we sometimes have a large number of update requirements, such as:\n- Need to replace the ID in the argument with another one\n- Need to replace a date parameter with a `${{today()}}` function call\n- Need to replace the host in the request with a new test address\n- Need to replace the token in the request header with the `${variable}` variable reference\n\nSome of the above updates can be done with the help of an IDE.\n\nFor the user's convenient use, `parrot`1.1.0 and later versions provide the `replace` command, which can be used to batch update the target use case set according to the rules specified by the user.\n\nSee section #1.2.5 for specific usage: `parrot help replace`\n\nFor example:\n\n`parrot replace -s demo/test_suites/test.yml -t demoNew -r 'xxID=>abcd, 2019-01-01=>${{today()}}, host=>example.com'`\n\nThe above command will automatically traverse all the yml files in the specified test_suite and its included test_cases and test_steps,\n\nand match the contents of `config.variables`, `request`, `request.headers`, `request.cookies` blocks.\n\nThe content before the '=>' symbol will be absolutely matched with the keys in the above use case block, and if it is missed, it will definitely match the values. \n\nIf some key or value matches, the value will be replaced with the content after the '=>' symbol.\n\n**If a replacement rule only needs to be valid for a particular interface, you can do this:**\n\n`parrot replace -s demo/test_suites/test.yml -t demoNew -r 'xxID=>abcd, apiA::2019-01-01=>${{today()}}'`\n\nThe above `2019-01-01=>${{today()}}` rule only works on the yml of the file name fuzzy matched `apiA`.\n\n***\n\n#### 1.3.5 Replay - Execute use cases, verify result, generate report\n\nAccording to the instructions in Section #1.2.3, we have a general understanding of the basic usage of the `playback` command. Now let's try to play back the `demo2` recorded earlier.\n\n**# A simple playback: -s & -t**\n\n```\n$ parrot playback -s demo2/test_suites -t demo2\n```\n\nIf it is successful, the process output information will be visible on the screen during the execution. After the execution is completed, you will see the generated test report in the `demo2` directory: `parrot_.html`, which could be viewed via PyCharm or browser.\n\n\n**# About the running order**\n\nParrot execute the test in the order of cases and steps defined in *test_suite*.yaml / *test_case*.yaml, currently only supports serial execution mode.\n\n> When the use case is automatically generated, the order of the steps defaults to the order of appearance in the recorded sample, which can be edited manually.\n\nThe detailed execution process:\n\n```\ntest_suite1\n |-> suite1.setup\n |-> test_case1\n |-> case1.setup\n |-> test_step1\n |-> step1.setup\n |-> request\n |-> validation\n |-> extract\n |-> step1.teardown\n |-> test_step2\n ...\n |-> case1.teardown\n |-> test_case2\n ...\n |-> suite1.teardown\ntest_suite2\n ...\n```\n\n**# About the running interval**\n\nThe value of `interval` argument is firstly used, otherwise the value `time.start` in step defination.\n\nIf the playback pass parameter specifies the `interval`, it will execute according to the interval (in milliseconds), for example:\n\n```\n$ parrot playback -s demo2/test_suites -t demo2 -i 100\n```\n\nOtherwise, if `time.start` is defined in the request of step (the recording phase will be automatically recorded), the default is to execute according to the interval of `time.start` of each step.\n\n> When the use case is automatically generated, the actual execution time would be recorded as `time.start` in the step defination.\n\n**# About the running validation**\n\nAs mentioned in the recording phase, each request generates some validations, which contain the expected result.\n\nIn the process of request playback, parrot can get the actual result in real time, so you can check the result in real time, and then check whether the value of each `check` object conforms to the `comparator` rule. If there is one failure, the entire step fails.\n\nAfter a single step fails, the current Parrot does not terminate the execution of the playback by default, but the user can perform some intervention by running the parameters:\n\n- --fail_stop: If specified, the operation will be terminated after a step verification fails\n- --fail\\_retry_times: The number of retries after a step failed, 0 as default\n- --fail\\_retry_interval: retry interval after a step failure\n\n***\n\n#### 1.3.6 Adapting multiple sets of environments\n\nParrot draws on Postman's environmental management mechanism.\n\n##### The environment configuration is automatically reserved during recording and can be edited manually.\n\nTake the recorded demo2 in section 1.3.2 as an example. The automatically generated environment is configured as `demo2/environments/sample_env.yml`. The default generated content is reserved for several sets of environment identifiers and the content is empty:\n\n```\ndevelopment: {}\nglobal: {}\nproduction: {}\ntest: {}\n```\n\nAt the same time, the `config` part of the automatically generated test\\_suites, test\\_cases and test\\_steps is referenced by `import` and `environment: global`, which can be edited manually.\n\n> `global` is globally shared, the rest are independent, and the new environment identifier can be customized.\n\nSuppose we have deployed multiple sets of `ParrotSample` applications, which represent different operating environments, separated by port number:\n\n```\ndevelopment: 8081\ntest: 8082\nproduction: 8080\n```\n\nWe hope that the same set of test cases can be reused in different environments. We can do like below:\n\nFirstly, edit `demo2/environments/sample_env.yml`:\n\n```\ndevelopment:\n host: 10.10.100.100:8081\nglobal:\n host: 10.10.100.100:8080\nproduction:\n host: 10.10.100.100:8080\ntest:\n host: 10.10.100.100:8082\n```\n\nThen, manually replace all the values \u200b\u200bof the `host` in all generated test_steps ymls with the `${host}` variable reference.\n\n##### Multiple sets of environment switching during playback\n\nAs mentioned in Section 1.2.3, the `parrot playback` command provides the `-env, --environment` parameter, which specifies the selected environment identifier at execution time.\n\n```\n$ parrot playback -s demo2/test_suites -t demo2 -env development\n```\n\nCurrently, the environment reference is included in the config of test\\_suites/test\\_cases/test\\_steps, and the `playback` parameter can also be specified. The load priority is:\n\n**parameter > test\\_suite.config > test\\_case.config > test\\_step.config**\n\n***\n\n## 2. Design ideas: starting from the original meaning of software testing\n\n### 2.1 How to define software testing\n\n**Classic definition of software testing:**\n\n> The process of using a **manual** or **automated** means to run or measure a software system, the purpose of which is to verify that it meets **specified requirements** or to clarify the difference between **expected** and **actual** results.\n>\n> -- *Software engineering terminology from IEEE in 1983*\n\n**A simplified definition:** \n> The process of running a system or application in accordance with defined requirements/steps, obtaining **actual result**, and comparing with the **expected result**.\n\n***\n\n### 2.2 How to do software testing\n\nLet's take the use case template of the ZenTao project management platform as an example. Everyone's daily use case design is basically similar, see the figure below:\n\n![](doc/TestCase.jpg)\n\nIf we want to extract key elements from it, based on the definition of software testing, we can easily find the following content:\n\n* `Steps`\n* `Expect` of each `Step`\n* Sometimes, in order to ensure complete execution, it is possible to add `Prerequisite`\n\nOther elements are usually designed to make use cases easier to manage. Think about it, do we usually do like this?\n\n***\n\n### 2.3 How to automate software testing\n\nWe use HTTP(S) interface test as an example to automate. There are usually two ways:\n\n#### 2.3.1 Use testing tools, such as: POSTMAN, JMETER\n\nTake Postman as an example. The implementation is roughly as shown in the figure below:\n\n![](doc/Postman.jpg)\n\n**Advantage of this approach:**\n\n1. Collection > Folder > Request, layered design makes the use case organization clearer\n2. With the environment variable management mechanism, it is convenient for common variable extraction and multiple sets of environment switching.\n3. `Pre-request Script` and `Tests` support pre- and post-actions, while `Tests` provides a richer validation methods that reduces the coding threshold.\n\n**Insufficient in this way:**\n\n1. The cost of creation is high, and each new Request requires a certain amount of effort to complete the addition, especially the `Query Params` and `Tests` sections. For dependencies between interfaces, it is necessary to write `Pre-request Script` more cumbersomely.\n2. The combination and order adjustment between Requests is not convenient enough, especially for Case or Suite with actual business logic.\n\n\n#### 2.3.2 Write your own automation framework, such as: Python+requests+unittest\n\nThe rough implementation, as in the example below:\n\n```\n# -*- coding: utf-8 -*-\n\nimport requests\nimport unittest\n\n\nclass TestHttpBin(unittest.TestCase):\n def setUp(self) -> None:\n \"\"\"do something here\"\"\"\n\n def test_get(self):\n url = 'http://httpbin.org/'\n params = {\n 'p1': 'v1',\n 'p2': 'v2'\n }\n response = requests.get(url=url, params=params)\n self.assertEqual(response.status_code, 200)\n\n def test_post(self):\n \"\"\"do something like test_get\"\"\"\n\n def tearDown(self) -> None:\n \"\"\"do something here\"\"\"\n\n\nif __name__ == '__main__':\n unittest.main()\n```\n\n**Advantage of this approach:**\n\n1. Flexible use case stratification, data-driven, let the use cases be arranged according to their own wishes.\n2. `requests` module encapsulates the underlying capabilities, we only need to write business logic code.\n3. `unittest` supports pre- and post- actions, while providing a richer verification methods and lowering the coding threshold.\n\n**Insufficient in this way:**\n\n1. The cost of creation/maintenance is high, and each new Request requires a certain amount of effort to complete the coding, especially the more assertions.\n2. There are certain technical thresholds for the design and writing of the framework, especially to ensure sufficient ease of use and versatility. For the dependencies between interfaces, the corresponding process variable transfer mechanism needs to be designed.\n\n***\n\n### 2.4 Is there a more convenient way to automate?\n\nComprehensive #2.3 chapter two kinds of automation methods, a lot of workload is reflected in:\n\n* A large number of Request/TestCase definitions, especially those with more parameters.\n* A large number of assertion method definitions, different Request verification points have differences.\n* Scenes that depend on other interfaces require more tedious processing of parameter passing.\n\n**\"Lazy\" is the first driving force of the technological advancement.**\n\nThe design idea of \u200b\u200bthe `parrot` tool is to solve the above problems, greatly improve the efficiency of automatic realization, and make automation easier.\n\n* A large number of Request/TestCase definitions can be implemented simply and quickly by `recording`.\n* Through `playback` mode, it can support the regular execution of automated use cases, and help you solve problems such as interface dependencies.\n* About verification, based on the recorded Request's Response, automatically generate some regular assertions and support secondary editing.\n\nCompare with the definition of software testing:\n\n- Recording: Get/Define specified requirement and **expected result**\n- Playback: Perform the recorded script to get the **actual result**\n- Verify: Compare the **expected** and **actual** results\n\n**Traffic playback** is a way to automate the realization of **the original definition of software testing**.\n\nThe design of this project is based on above idea to automate the interface testing.\n\n## 3. Source Code\n\n### 3.1 GitHub\n\nThis project: \n\nIts sample project: \n\nAll of them contain a detailed README for your reference.\n\nIf you have any questions or suggestions, please feel free to submit an issue within the project.\n\n### 3.2 Framework Structure\n```\niparrot/\n \u251c\u2500\u2500 modules\n \u2502\u00a0\u00a0 \u251c\u2500\u2500 helper.py : A collection of commonly used methods in which the Function can be used in other modules, also supporting the use of ${{function(params)}} in the cases.\n \u2502\u00a0\u00a0 \u251c\u2500\u2500 request.py : Execute HTTP(S) request based on `requests` and get the result\n \u2502\u00a0\u00a0 \u251c\u2500\u2500 validator.py : The verification engine for request's response information, which supports multiple verification rules, as detailed in Validator.UNIFORM_COMPARATOR\n \u2502\u00a0\u00a0 \u251c\u2500\u2500 logger.py : Formatted log printing, support for output to screen or log files\n \u2502\u00a0\u00a0 \u2514\u2500\u2500 reportor.py : Standardized report printing, support for views of summary results and use case execution details\n \u251c\u2500\u2500 extension\n \u2502\u00a0\u00a0 \u2514\u2500\u2500 helper.py : A collection of common methods that can be customized by the user, where the Function supports use as ${{function(params)}} in the cases\n \u251c\u2500\u2500 parser.py : Parse the source file, and automatically generate formatted use cases; parse the specified use case set, load into memory\n \u251c\u2500\u2500 player.py : Play back the specified set of use cases, execute them in levels, and finally generate a test report\n \u2514\u2500\u2500 parrot.py : The main script, you can run `python parrot.py help` to see the specific usage\n\n```\n\n## 4. Appendix\n\n### 4.1 Use case structure\n\nExample:\n\n- **environment**\n\n\t```yaml\n\tglobal: {}\n\tproduction: {}\n\tdevelopment: {}\n\ttest: {}\n\t```\n- **test_step**\n\n\t```yaml\n\tconfig:\n\t environment: \n\t import: \n\t name: step name\n\t variables:\n\t p1: ABC\n\t p2: 123\n\trequest:\n\t method: POST\n\t protocol: http\n\t host: x.x.x.x:8000\n\t url: /path/of/api\n\t params: {}\n\t data:\n\t param1: ${p1}\n\t param2: ${p2}\n\t headers:\n\t Content-Type: application/json; charset=UTF-8\n\t cookies: {}\n\t time.start: 1568757525027\n\tresponse:\n\t extract: {}\n\tsetup_hooks: []\n\tteardown_hooks: []\n\tvalidations:\n\t- eq:\n\t status.code: 200\n\t- exists:\n\t headers.token\n\t- is_json:\n\t content\n\t- eq:\n\t content.code: 100\n\t```\n- **test_case**\n\n\t```yaml\n\tconfig:\n\t environment: \n\t import: \n\t name: case name\n\t variables: {}\n\tsetup_hooks: []\n\tteardown_hooks: []\n\ttest_steps:\n\t - \n\t - \n\t```\n- **test_suite**\n\n\t```yaml\n\tconfig:\n\t environment: \n\t import: \n\t name: suite name\n\t variables: {}\n\tsetup_hooks: []\n\tteardown_hooks: []\n\ttest_cases: \n\t - \n\t - \n\t```\n\n***\n\n### 4.2 Verification method set\n\nThese methods can be used in validations in specific test_steps, for example:\n\n```yaml\nvalidations:\n- eq:\n status.code: 200\n- is_json:\n content\n- not_null:\n headers.token\n- contains:\n content.message: succ\n```\n\n**Commonly used verification methods:**\n\n- **eq(equals):**\n\t- Example: `1 eq 1`, `'a' eq 'a'`, `[1, 2] eq [1, 2]`, `{'a': 1 } eq {'a': 1}`, `status.code eq 200`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- eq:\n\t\t status.code: 200\n\t\t- eq:\n\t\t headers.Content-Type: application/json;charset=UTF-8\n\t\t- eq:\n\t\t content.data[0].id: 1000\n\t\t```\n\t- Similar methods: `neq`, `lt`, `gt`, `le`, `ge`\n- **len_eq(length equals):**\n\t- Example: `'ab' len_eq 2`, `[1, 2] len_eq 2`, `{'a': 1} len_eq 1`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- len_eq:\n\t\t headers.token: 32\n\t\t- eq:\n\t\t content.datalist: 3\n\t\t```\n\t- Similar methods: `len_neq`, `len_lt`, `len_gt`\n- **time_le(time spent less than or equals):**\n\t- Example: `request 'time.spent' time_le 200 (unit is ms)`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- time_le:\n\t\t time.spent: 200\n\t\t```\n\t- Similar methods: `time_lt`, `time_gt`, `time_ge`\n- **contains:**\n\t- Example: `'abc' contain 'ab', ['a', 'b'] contain 'a', {'a': 1, 'b': 2} contain {'a': 1}`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- contains:\n\t\t headers.Content-Type: application/json\n\t\t- contains:\n\t\t content.message: ok\n\t\t```\n\t- Similar methods: `not_contains`\n- **in:**\n\t- Example: `'a' in 'ab'`, `'a' in ['a', 'b']`, `'a' in {'a': 1, 'b': 2}`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- in:\n\t\t status.code: [200, 302]\n\t\t```\n\t- Similar methods: `not_in`\n- **is_false:**\n\t- Example: `0 is_false`, `'' is_false`, `[] is_false`, `{} is_false`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- is_false:\n\t\t content.datalist\n\t\t- is_json:\n\t\t content\n\t\t- is_instance:\n\t\t status.code: int\n\t\t```\n\t- Similar methods: `is_true`, `exists`, `is_instance`, `is_json`\n- **re(regex):**\n\t- Example: `'1900-01-01' re r'\\d+-\\d+-\\d+'`\n\t- Usage:\n\n\t\t```\n\t\tvalidations:\n\t\t- re:\n\t\t content.data[0].date: r\"\\d+-\\d+-\\d+\"\n\t\t```\n\t- Similar methods: `not_re`\n\nMore methods and instructions can be found in the following way:\n\n```python\nimport json\nfrom iparrot.modules.validator import Validator\n\nprint(json.dumps(Validator.UNIFORM_COMPARATOR, indent=4))\n\n```\n\n***\n\n### 4.3 Helper method set\n\nThese methods can be applied to the inside of test_step / test_case / test_suite in the form `${{function(params)}}`, such as: `setup_hooks` `teardown_hooks` `variables`\n\n```\ntoday(form='%Y-%m-%d'): Get today's date\n\ndays_ago(days=0, form='%Y-%m-%d'): Get the date a few days ago\n\ndays_later(days=0, form='%Y-%m-%d'): Get the date a few days later\n\nnow(form='%Y-%m-%d %H:%M:%S'): Get the current time, accurate to the second\n\nnow_ms(form='%Y-%m-%d %H:%M:%S'): Get the current time, accurate to the micro second\n\nnow_timestamp(): Get the current timestamp, accurate to the second\n\nnow_timestamp_ms(): Get the current timestamp, accurate to the micro second\n\nhours_ago(hours=0, form='%Y-%m-%d %H:%M:%S'): Get the time a few hours ago, accurate to the second\n\nhours_later(hours=0, form='%Y-%m-%d %H:%M:%S'): Get the time a few hours later, accurate to the second\n\nget_file_name(file, ext=0): Intercept the file name of the specified file, no suffix by default\n\nget_file_path(file): Intercept the path of the specified file\n\nmake_dir(directory): Generate the specified directory\n\ncopy_file(source, target): Copy the specified file to the target path\n\nget_random_integer(length=10, head=None, tail=None): Generate random number, with specified length, head, tail\n\nget_random_string(length=10, simple=1, head=None, tail=None): Generate random string, with specified length, head, tail\n\nget_random_phone(head=None, tail=None): Generate random Chinese phone number, with specified length, head, tail\n```\n\n> If the above helper methods do not meet your needs, you can use the following method:\n> \n> Define your own module or pip install specified module in your local environment.\n> \n> Add `import xxx` or `from xxx import yyy` to the desired step / case / suite `setup_hooks`, \n> \n> then you could use the `${{function(params)}}` format to call the method you want.\n> \n> Any questions, you can feedback an issue: \n> \n> More general helper methods, welcome to contribute code or issues, thanks.\n\n## 5. External reference, thanks\n### 5.1 [Postman](https://learning.getpostman.com/)\n\n#### 5.1.1 Environments management\nThe mechanism is referenced in the `environment` of the Parrot use case structure.\n\n```\nA project can be configured with multiple sets of environments to hold some common environment variables.\n\nVariable names are consistent between different environments, and values \u200b\u200bcan vary.\n\nIn the use case, you can refer to the variable by means of ${variable}, reducing manual modification.\n\nThe switching of the operating environment can be specified in the playback phase by the -env/--environment parameter.\n```\n\n#### 5.1.2 Use case layering mode\n - Collection => test_suite\n - Folder => test_case\n - Request => test_step\n\n#### 5.1.3 Pre and post actions\n - Pre-request Script => setup_hooks\n - Tests => teardown_hooks & validations\n\n### 5.2 [HttpRunner](https://github.com/httprunner/httprunner)\n\n#### 5.2.1 [HAR2Case](https://github.com/HttpRunner/har2case)\n\nThe files processed by Parrot in the first phase are Charles trace and Fiddler txt. The format is quite different, and the parsing of plain text is cumbersome.\n\nLater, in the course of HttpRunner's ideas, I used HAR to reconstruct the record part. At the same time, I made some changes in the parameters.\n\nInspired by HttpRunner's ideas, the record part is rebuilt, and some paramters are updated.\n\nFor details, to see `parrot help record` and `iparrot.parser`\n\n#### 5.2.2 Use case layering mode\n\nThe use case layering mode of HttpRunner, TestSuite>TestCase>TestStep, is clear and a good reference.\n\nWhen Parrot automatically generates use cases, it directly implements the layering mode on the directory structure and changes the specific use case structure.\n\n#### 5.2.3 setup hooks & teardown hooks\n\nParrot reuses this naming scheme, which supports `set variable`, `call function`, `exec code`.\n\n#### 5.2.4 extract variable\n\nParrot in the first phase uses the mode of `store` and `replace`, which is intended to keep all changes in a configuration file, and does not invade the use case at all. \n\nIn actual use, it is found that the usability is not good and the configuration is slightly cumbersome.\n\nRefer to HttpRunner, return the initiative to the user, and the variable can be extracted according to `extract` defination and used as `${variable}`.\n\n#### 5.2.5 comparator\n\nThe first version of Parrot diffs results refer to a configuration file, only supports `eq` and simple `re`, and the method set is limited.\n\nNow refer to the HttpRunner, automatically generate `eq` comparator when recording, and support a variety of comparator customization.\n\nComparators in Parrot combines with the common verification methods of HttpRunner and Postman, and a certain supplement.\n\n#### 5.2.6 report\n\nParrot's test report template directly reuses HttpRunner's report style.\n\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/idle-man/iParrot", "keywords": "test automate automation record replay playback parrot iparrot http", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "iParrot", "package_url": "https://pypi.org/project/iParrot/", "platform": "", "project_url": "https://pypi.org/project/iParrot/", "project_urls": { "Bug Reports": "https://github.com/idle-man/iParrot/issues", "Documentation": "https://github.com/idle-man/iParrot/blob/master/README.md", "Homepage": "https://github.com/idle-man/iParrot", "Source": "https://github.com/idle-man/iParrot" }, "release_url": "https://pypi.org/project/iParrot/1.1.4/", "requires_dist": [ "PyYAML", "requests", "check-manifest ; extra == 'dev'", "coverage ; extra == 'test'" ], "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "summary": "Automated test solution for http requests based on recording and playback", "version": "1.1.4", "yanked": false, "yanked_reason": null }, "last_serial": 11525710, "releases": { "1.0.1": [ { "comment_text": "", "digests": { "md5": "56382ae0240c3b8203b72892fa8714f5", "sha256": "a98e6746d5f2ccb18593cbd6b0bb7fff006d4111b6227734ce7be4ec216cf917" }, "downloads": -1, "filename": "iParrot-1.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "56382ae0240c3b8203b72892fa8714f5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 34096, "upload_time": "2019-09-27T07:27:41", "upload_time_iso_8601": "2019-09-27T07:27:41.945157Z", "url": "https://files.pythonhosted.org/packages/54/7a/576d6bf4cb07331982861f8b599f6ec0fc0f51bf1e4654555d17b4c242dc/iParrot-1.0.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "c9f8f040aa7be41b2724116cb2c262e9", "sha256": "927d036bced8a549b6bd782cf0a64ad4f2b29b567e46e379e9ba2dfd63e52581" }, "downloads": -1, "filename": "iParrot-1.0.1.tar.gz", "has_sig": false, "md5_digest": "c9f8f040aa7be41b2724116cb2c262e9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32992, "upload_time": "2019-09-27T07:27:44", "upload_time_iso_8601": "2019-09-27T07:27:44.005916Z", "url": "https://files.pythonhosted.org/packages/66/26/c31d9e71141fe9db8cba5a5e534547d994b7a20809a975283e54d773d59e/iParrot-1.0.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "df10268736fc0551d2a33e044a79a841", "sha256": "3fe1835eb667c9ef25258724f6cd1068685a4cf53d5fc9ea893a8f76ee3ef968" }, "downloads": -1, "filename": "iParrot-1.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "df10268736fc0551d2a33e044a79a841", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 34129, "upload_time": "2019-09-29T03:32:00", "upload_time_iso_8601": "2019-09-29T03:32:00.048083Z", "url": "https://files.pythonhosted.org/packages/e5/9b/d4697d8712749d45c4d23d4132d990cc5aeffe2e73fc9e7e45359da33d47/iParrot-1.0.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "3db41069979cef4087583fbbc7c1a1b6", "sha256": "c8f344219b6fd78f1fee284f17abf09570feb97d85c9350b7c8539fc663ea51a" }, "downloads": -1, "filename": "iParrot-1.0.2.tar.gz", "has_sig": false, "md5_digest": "3db41069979cef4087583fbbc7c1a1b6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32954, "upload_time": "2019-09-29T03:32:02", "upload_time_iso_8601": "2019-09-29T03:32:02.346790Z", "url": "https://files.pythonhosted.org/packages/ac/ad/9aa9554100feccbd3c05b62365af4188fb7ff2a1fa6d054af84d8c94e823/iParrot-1.0.2.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.3": [ { "comment_text": "", "digests": { "md5": "80e0a44094d4455c859630dd401f9fdc", "sha256": "3540b1045c84b8eb78884f8b4ff45da2f9dd57bfb8e0f1c740cda7459a1e2498" }, "downloads": -1, "filename": "iParrot-1.0.3-py3.7.egg", "has_sig": false, "md5_digest": "80e0a44094d4455c859630dd401f9fdc", "packagetype": "bdist_egg", "python_version": "3.7", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 69791, "upload_time": "2019-10-24T03:42:48", "upload_time_iso_8601": "2019-10-24T03:42:48.517656Z", "url": "https://files.pythonhosted.org/packages/93/fb/64d890aad331e98f8010b33c322398f343ab5330a613d11e5d106cbbc03a/iParrot-1.0.3-py3.7.egg", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "91be8c4833947f67c7042dac4c0afefd", "sha256": "c0b72c13f72d25827a88fb05e449fd4294a7fe67b397f5e9f5405e95d0beeebe" }, "downloads": -1, "filename": "iParrot-1.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "91be8c4833947f67c7042dac4c0afefd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 37437, "upload_time": "2019-10-24T03:42:46", "upload_time_iso_8601": "2019-10-24T03:42:46.038784Z", "url": "https://files.pythonhosted.org/packages/80/d6/6d793ac8584780a8ba7f48e94a58496fc0af7bf5d4b14f5a55b4ce0bb2f8/iParrot-1.0.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "217a250632cf7909d8aa545106d4069a", "sha256": "855158104b543a8d71897f8fc13a3645345a5171f2ed2621eded1f1149d39626" }, "downloads": -1, "filename": "iParrot-1.0.3.tar.gz", "has_sig": false, "md5_digest": "217a250632cf7909d8aa545106d4069a", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 60461, "upload_time": "2019-10-24T03:42:50", "upload_time_iso_8601": "2019-10-24T03:42:50.954637Z", "url": "https://files.pythonhosted.org/packages/c6/cd/0343d764a49be0b3d657ef7cf83711ced9ea3cecb1c34d2b2197eda21a62/iParrot-1.0.3.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.4": [ { "comment_text": "", "digests": { "md5": "899c9e2d96a763d3723aa2f890549b5e", "sha256": "12632fbdae2a1936a70cba1f4d3eceafcd654b0aca7a4ff5135250eaea0c9b26" }, "downloads": -1, "filename": "iParrot-1.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "899c9e2d96a763d3723aa2f890549b5e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 38268, "upload_time": "2019-10-25T07:15:00", "upload_time_iso_8601": "2019-10-25T07:15:00.879610Z", "url": "https://files.pythonhosted.org/packages/67/13/725d14d62b61af12e600285443e3c5b23d358ecd56b9335eb44f0a0b5c33/iParrot-1.0.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "cbb597d55f7a5bde68ef1da95ddfa065", "sha256": "a3dbc225905d67d0d4edb79b8fe6f1f5075c16ceb0fffd338693aeeb3f0cf079" }, "downloads": -1, "filename": "iParrot-1.0.4.tar.gz", "has_sig": false, "md5_digest": "cbb597d55f7a5bde68ef1da95ddfa065", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 61364, "upload_time": "2019-10-25T07:15:03", "upload_time_iso_8601": "2019-10-25T07:15:03.505774Z", "url": "https://files.pythonhosted.org/packages/6a/5f/e0c9d11b77100e41ab0fb49992d9f78f441290dc120f81afd0a670b1547f/iParrot-1.0.4.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.5": [ { "comment_text": "", "digests": { "md5": "bbe038b08396758a17c37faed6ddc80a", "sha256": "81d7498ff485b31721e516d1b5deb7cb5ac45f7c835e3501fe3b79c55c5c27ef" }, "downloads": -1, "filename": "iParrot-1.0.5-py3-none-any.whl", "has_sig": false, "md5_digest": "bbe038b08396758a17c37faed6ddc80a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 39461, "upload_time": "2019-10-30T09:21:18", "upload_time_iso_8601": "2019-10-30T09:21:18.543234Z", "url": "https://files.pythonhosted.org/packages/9c/b6/b5eaa6774d2e246409592bff21dda9bf187a5837baa350b0219cf0e58bec/iParrot-1.0.5-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "ef9d20237c98db3842f7c165d06cef81", "sha256": "fe4f46c28f3430dba2484ad0f1fe992e44d0606b98ca1a96b69601f9e8b27f5d" }, "downloads": -1, "filename": "iParrot-1.0.5.tar.gz", "has_sig": false, "md5_digest": "ef9d20237c98db3842f7c165d06cef81", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 67356, "upload_time": "2019-10-30T09:21:21", "upload_time_iso_8601": "2019-10-30T09:21:21.999583Z", "url": "https://files.pythonhosted.org/packages/dc/52/df3fe6203e79d9d639a9a1e5921b3651f1b5a489e436fd5863bbb19745f8/iParrot-1.0.5.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.6": [ { "comment_text": "", "digests": { "md5": "c4425ad52c8560c7db5eea2b253d64e0", "sha256": "c87183848d68aab6c0500dd17a0d26dfc2656a82e395649fd2bb11945e8de755" }, "downloads": -1, "filename": "iParrot-1.0.6-py3-none-any.whl", "has_sig": false, "md5_digest": "c4425ad52c8560c7db5eea2b253d64e0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 39808, "upload_time": "2019-11-08T07:28:10", "upload_time_iso_8601": "2019-11-08T07:28:10.018332Z", "url": "https://files.pythonhosted.org/packages/e3/fb/78021a6d5e84742a036d56ba3237c534663aec8e67c90c52ad2a43ad6c66/iParrot-1.0.6-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "5d080f63e43f20fbd3042c05f203ee50", "sha256": "393b2993a5d6c2989ca5747e5e59ccf247bfec433750aec0cad3c13892b00bbd" }, "downloads": -1, "filename": "iParrot-1.0.6.tar.gz", "has_sig": false, "md5_digest": "5d080f63e43f20fbd3042c05f203ee50", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 69103, "upload_time": "2019-11-08T07:28:12", "upload_time_iso_8601": "2019-11-08T07:28:12.480029Z", "url": "https://files.pythonhosted.org/packages/7b/5c/604cf26e508404e17870339653236418b568a9b17937a34b80c191d2d9e6/iParrot-1.0.6.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "8e081ad921dbeb1413f9ffadb33cb086", "sha256": "dc97a148cf0552a9e5e4741df357e58fd695c3fd81870395b37162f534cc627e" }, "downloads": -1, "filename": "iParrot-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "8e081ad921dbeb1413f9ffadb33cb086", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 43977, "upload_time": "2019-11-18T08:45:12", "upload_time_iso_8601": "2019-11-18T08:45:12.053343Z", "url": "https://files.pythonhosted.org/packages/dd/b2/552c8f8e5468785d6ea2dbb653508c57cac8b69e8032d617162348b1bc30/iParrot-1.1.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "56fb7765da0ea7d21f205e97bc491296", "sha256": "e435157b48c184b9082ebb40d070924f2a48302524d9d813989b2033aa95a4eb" }, "downloads": -1, "filename": "iParrot-1.1.1.tar.gz", "has_sig": false, "md5_digest": "56fb7765da0ea7d21f205e97bc491296", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 79273, "upload_time": "2019-11-18T08:45:16", "upload_time_iso_8601": "2019-11-18T08:45:16.131216Z", "url": "https://files.pythonhosted.org/packages/b6/46/318048ac2763d583e1d5abcec1014f6dc6a6cdf7abd86347ac51b8fc544e/iParrot-1.1.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "d7cb78375a8d2e26cfaebe9491e02ae2", "sha256": "101f6ae768064368a2f0ec631ab7e93b264865863f6ad723afc896db864571aa" }, "downloads": -1, "filename": "iParrot-1.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "d7cb78375a8d2e26cfaebe9491e02ae2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 44095, "upload_time": "2020-11-17T03:00:05", "upload_time_iso_8601": "2020-11-17T03:00:05.067562Z", "url": "https://files.pythonhosted.org/packages/a0/0c/71053db30b0fad313ae3a90b92155541d2deec67737b39af16c85fedfa2c/iParrot-1.1.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "c147e252e202e8f15ac6d0dedb608330", "sha256": "9d4dcab87d32ee2c81459b0c05dfa6d58ccc5d2ac25bb2f66ea250b185372c0d" }, "downloads": -1, "filename": "iParrot-1.1.2.tar.gz", "has_sig": false, "md5_digest": "c147e252e202e8f15ac6d0dedb608330", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 79425, "upload_time": "2020-11-17T03:00:08", "upload_time_iso_8601": "2020-11-17T03:00:08.231690Z", "url": "https://files.pythonhosted.org/packages/0a/e1/7a69ec29dfd79d3b9bf73968d55a760bb8ab71a969e4a5525930f64dac27/iParrot-1.1.2.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.3": [ { "comment_text": "", "digests": { "md5": "e8fddcc8c2f224c682def433d24c2978", "sha256": "61aa4c1fe88657aef63e494fd8551c12182fdf0a10636c0dd1c1370d77936223" }, "downloads": -1, "filename": "iParrot-1.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "e8fddcc8c2f224c682def433d24c2978", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 44108, "upload_time": "2021-05-07T06:49:59", "upload_time_iso_8601": "2021-05-07T06:49:59.903995Z", "url": "https://files.pythonhosted.org/packages/80/f5/6de2ea5a4fc0986f94eb85453a2eec078e781b895ea97d8b044780e58d4e/iParrot-1.1.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "23016101cf4f2dfa3d234a34f237b7c8", "sha256": "b9cbeb75d63ca5586be3414f0fbf82241b27d0a8338c3bcc2300888dc86a62b7" }, "downloads": -1, "filename": "iParrot-1.1.3.tar.gz", "has_sig": false, "md5_digest": "23016101cf4f2dfa3d234a34f237b7c8", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 80691, "upload_time": "2021-05-07T06:50:02", "upload_time_iso_8601": "2021-05-07T06:50:02.590507Z", "url": "https://files.pythonhosted.org/packages/6f/4b/79a7e078859679442878fd36e411f71f80dfff70cc06c80a4d064a7cbe41/iParrot-1.1.3.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.4": [ { "comment_text": "", "digests": { "md5": "07e362ceb71991c5e0e88fc73ac7f9fa", "sha256": "752f6cf71cbd119631a897120fb4813213e4a19d83d6830d9b932a42fe9be933" }, "downloads": -1, "filename": "iParrot-1.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "07e362ceb71991c5e0e88fc73ac7f9fa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 44109, "upload_time": "2021-09-23T06:03:00", "upload_time_iso_8601": "2021-09-23T06:03:00.634882Z", "url": "https://files.pythonhosted.org/packages/86/79/9973a2c2f624c45ce25367f1580f748cb698f1798dc151eeeb25ec44314d/iParrot-1.1.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "14ba71d7f06ba14be4f1bad2f08d06e4", "sha256": "2a5ea5dff0cfb2dd5b0f19f2f1001d18bdbc32a9ed2e09cd5467f484bd6e2881" }, "downloads": -1, "filename": "iParrot-1.1.4.tar.gz", "has_sig": false, "md5_digest": "14ba71d7f06ba14be4f1bad2f08d06e4", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 80720, "upload_time": "2021-09-23T06:03:03", "upload_time_iso_8601": "2021-09-23T06:03:03.266795Z", "url": "https://files.pythonhosted.org/packages/21/24/59e61ce77ada99cbc63392bb71341635d3da6227e6b5ca26fac311fba85c/iParrot-1.1.4.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "07e362ceb71991c5e0e88fc73ac7f9fa", "sha256": "752f6cf71cbd119631a897120fb4813213e4a19d83d6830d9b932a42fe9be933" }, "downloads": -1, "filename": "iParrot-1.1.4-py3-none-any.whl", "has_sig": false, "md5_digest": "07e362ceb71991c5e0e88fc73ac7f9fa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 44109, "upload_time": "2021-09-23T06:03:00", "upload_time_iso_8601": "2021-09-23T06:03:00.634882Z", "url": "https://files.pythonhosted.org/packages/86/79/9973a2c2f624c45ce25367f1580f748cb698f1798dc151eeeb25ec44314d/iParrot-1.1.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "14ba71d7f06ba14be4f1bad2f08d06e4", "sha256": "2a5ea5dff0cfb2dd5b0f19f2f1001d18bdbc32a9ed2e09cd5467f484bd6e2881" }, "downloads": -1, "filename": "iParrot-1.1.4.tar.gz", "has_sig": false, "md5_digest": "14ba71d7f06ba14be4f1bad2f08d06e4", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4", "size": 80720, "upload_time": "2021-09-23T06:03:03", "upload_time_iso_8601": "2021-09-23T06:03:03.266795Z", "url": "https://files.pythonhosted.org/packages/21/24/59e61ce77ada99cbc63392bb71341635d3da6227e6b5ca26fac311fba85c/iParrot-1.1.4.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }