diff --git a/courseProjectCode/Metrics/README.md b/courseProjectCode/Metrics/README.md new file mode 100644 index 0000000000..96bd824bd8 --- /dev/null +++ b/courseProjectCode/Metrics/README.md @@ -0,0 +1,124 @@ +# Metrics Collection + +Collects the two quality metrics for our course project — **Maintainability** +and **Testability** — from the forked project's Python source and writes them to +reproducible output files. Every number in our report comes from running this. + +## Requirements + +- Python 3.9+ +- `radon` (static analysis: Maintainability Index, cyclomatic complexity, Halstead, raw LOC) + +```bash +pip install radon +``` + +## Running it + +From the repository root: + +```bash +python courseProjectCode/Metrics/collect_metrics.py -o courseProjectCode/Metrics/output +``` + +Example, analyzing the package we forked: + +```bash +python courseProjectCode/Metrics/collect_metrics.py ./httpie -o courseProjectCode/Metrics/output +``` + +Test directories, docs, build artifacts, and virtualenvs are excluded by +default. Override with `--exclude`: + +```bash +python courseProjectCode/Metrics/collect_metrics.py ./src --exclude tests docs build +``` + +## Output + +| File | Contents | +| --- | --- | +| `metrics_per_file.csv` | One row per source file — all raw and derived metrics | +| `metrics_summary.json` | Project-level rollup, MI risk bands, and the ten least maintainable / least testable files | + +Both files are regenerated from scratch on every run, so the results in the +report are reproducible from a clean checkout. + +## Metric 1 — Maintainability + +Measured with the **Maintainability Index (MI)**, the standard composite of +Halstead volume, cyclomatic complexity, and lines of code, computed by `radon` +(`mi_visit`). Radon reports MI on a 0–100 scale and applies conventional bands: + +| MI | Interpretation | +| --- | --- | +| 100–65 | Maintainable | +| 65–20 | Moderate maintenance risk | +| 20–0 | High maintenance risk / difficult to maintain | + +The summary file reports the mean and median MI and counts how many files fall +below each threshold. Supporting values are also captured per file so the score +can be interpreted rather than taken on faith: SLOC, comment ratio, average and +maximum cyclomatic complexity, and Halstead volume and difficulty. + +## Metric 2 — Testability + +There is no single agreed-upon testability metric the way there is for +maintainability, so this tool reports the **structural properties known to drive +test effort** and combines them into an explicitly-defined heuristic score. +Treat the score as a ranking device for finding the hard-to-test parts of the +system, not as an absolute measurement. + +Four factors, each a form of work a test author has to absorb: + +| Factor | Column | Why it costs test effort | +| --- | --- | --- | +| Complexity | `avg_cyclomatic_complexity` | More independent paths to cover for the same behavior | +| Coupling | `fan_out_internal` + `fan_out_external` | Every collaborator must be constructed, stubbed, or mocked | +| Parameters | `avg_parameters` | Larger setup burden per test case | +| Public surface | `public_definitions` | More entry points that each need their own tests | + +Each factor is normalized to 0–1 against a saturation point, weighted, and +subtracted from 100: + +``` +testability = (1 - Σ wᵢ · min(xᵢ / sᵢ, 1)) × 100 +``` + +| Factor | Weight | Saturation point | +| --- | --- | --- | +| Complexity | 0.40 | 10 (the conventional "refactor this" threshold for CC) | +| Coupling | 0.30 | 20 imported modules | +| Parameters | 0.15 | 5 parameters | +| Public surface | 0.15 | 30 public functions/classes | + +**The weights and saturation points are our assumption, not a published +standard.** They are defined at the top of `collect_metrics.py` in the `WEIGHTS` +and `SATURATION` dictionaries and can be changed in one place; the report +justifies the choice and notes the sensitivity. + +Coupling is resolved properly rather than counted naively: imports are matched +against the project's own module index, so internal coupling (project modules) +and external coupling (third-party libraries) are reported separately, and +relative imports (`from . import x`) are resolved to real modules. `fan_in_internal` +records how many project modules import a given file, which identifies the +high-blast-radius modules where a regression is most expensive. + +### Known limitations + +- Static analysis only — it does not measure whether existing tests are *good*. + Mutation testing (e.g. `mutmut`) or coverage would complement this and is a + reasonable extension for a later deliverable. +- Dynamic constructs (reflection, `importlib`, monkeypatching) are invisible to + the AST walk, so coupling is a lower bound. +- Files that fail to parse are skipped and reported on stderr. + +## Reproducing the report numbers + +```bash +pip install radon +python courseProjectCode/Metrics/collect_metrics.py -o courseProjectCode/Metrics/output +``` + +Then read `metrics_summary.json` for the project-level figures quoted in the +report and `metrics_per_file.csv` for the per-file tables. diff --git a/courseProjectDocs/Setup/README.md b/courseProjectDocs/Setup/README.md new file mode 100644 index 0000000000..6ca2e0e2aa --- /dev/null +++ b/courseProjectDocs/Setup/README.md @@ -0,0 +1,13 @@ +# Reproduce + +Python 3.11, from repo root: + +```bash +python -m venv venv +venv/Scripts/pip install -e ".[dev]" pytest-html +venv/Scripts/python -m pytest --cov=httpie --cov-branch \ + --cov-report=html:courseProjectDocs/Setup/testCoverage \ + --html=courseProjectDocs/Setup/testResults/report.html --self-contained-html +``` + +Open `testResults/report.html` and `testCoverage/index.html`. diff --git a/courseProjectDocs/Setup/report.md b/courseProjectDocs/Setup/report.md new file mode 100644 index 0000000000..5cfbbd7d81 --- /dev/null +++ b/courseProjectDocs/Setup/report.md @@ -0,0 +1,22 @@ +# Baseline Build & Test Report HTTPie CLI + +## Environment +Windows 10, Python 3.11.15, `venv` + `pip install -e ".[dev]" pytest-html`, pytest 9.1.1, pytest-cov 7.1.0. + +## Test Suite +1,028 pytest cases (37 modules + doctests). Mostly integration tests against a local httpbin server; unit tests for parsers/formatters; 33 system tests (installed CLI / subprocesses); 4 UI (`--help`) tests. + +## Results +| Run | Passed | Failed | Skipped | xfail | +|---|---|---|---|---| +| 1,028 | 1,003 | 2 | 19 | 4 | + +Failures: 2 Big5 charset-detection tests in `test_encoding.py`. Skips: mostly Unix-only tests on Windows. + +## Coverage +Statement 90.7% (3,797/4,187), branch 80.9% (911/1,126). + +## Observations +- UI code barely covered: `man_pages.py`, `rich_utils.py` 0%, `rich_help.py` 53%. +- Platform-specific code (`compat.py`, `daemons.py`) only partly covered on one OS. +- Branch coverage ~10 points below statement: error paths not tested. diff --git a/courseProjectDocs/Setup/testCoverage/class_index.html b/courseProjectDocs/Setup/testCoverage/class_index.html new file mode 100644 index 0000000000..c0f0137f1c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/class_index.html @@ -0,0 +1,2878 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 89% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.16.2, + created at 2026-09-28 11:59 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   Statements Branches Total
Fileclass coveragestatementsmissingexcluded coveragebranchespartial coverage
httpie\__init__.py(no class) 100%400 100%00 100%
httpie\__main__.py(no class) 100%803 100%00 100%
httpie\adapters.pyHTTPieHTTPAdapter 100%300 100%00 100%
httpie\adapters.py(no class) 100%400 100%00 100%
httpie\cli\__init__.py(no class) 100%000 100%00 100%
httpie\cli\argparser.pyHTTPieHelpFormatter 89%910 50%21 82%
httpie\cli\argparser.pyBaseHTTPieArgumentParser 95%2010 75%82 89%
httpie\cli\argparser.pyHTTPieManagerArgumentParser 100%600 100%20 100%
httpie\cli\argparser.pyHTTPieArgumentParser 92%234180 91%1348 92%
httpie\cli\argparser.py(no class) 100%4700 100%00 100%
httpie\cli\argtypes.pyKeyValueArg 83%610 100%00 83%
httpie\cli\argtypes.pySessionNameValidator 75%410 50%21 67%
httpie\cli\argtypes.pyEscaped 0%110 100%00 0%
httpie\cli\argtypes.pyKeyValueArgType 100%3200 100%180 100%
httpie\cli\argtypes.pyPromptMixin 43%740 100%00 43%
httpie\cli\argtypes.pySSLCredentials 100%200 100%00 100%
httpie\cli\argtypes.pyAuthCredentials 100%200 100%00 100%
httpie\cli\argtypes.pyAuthCredentialsArgType 100%400 100%00 100%
httpie\cli\argtypes.py(no class) 100%7800 100%120 100%
httpie\cli\constants.pyPrettyOptions 100%000 100%00 100%
httpie\cli\constants.pyRequestType 100%000 100%00 100%
httpie\cli\constants.py(no class) 100%4600 100%00 100%
httpie\cli\definition.py(no class) 93%10880 50%63 90%
httpie\cli\dicts.pyBaseMultiDict 100%000 100%00 100%
httpie\cli\dicts.pyHTTPHeadersDict 90%1010 83%61 88%
httpie\cli\dicts.pyRequestJSONDataDict 100%000 100%00 100%
httpie\cli\dicts.pyMultiValueOrderedDict 100%1100 90%101 95%
httpie\cli\dicts.pyRequestQueryParamsDict 100%000 100%00 100%
httpie\cli\dicts.pyRequestDataDict 100%000 100%00 100%
httpie\cli\dicts.pyMultipartRequestDataDict 100%000 100%00 100%
httpie\cli\dicts.pyRequestFilesDict 100%000 100%00 100%
httpie\cli\dicts.py(no class) 100%1900 100%00 100%
httpie\cli\exceptions.pyParseError 100%000 100%00 100%
httpie\cli\exceptions.py(no class) 100%200 100%00 100%
httpie\cli\nested_json\__init__.py(no class) 100%400 100%00 100%
httpie\cli\nested_json\errors.pyNestedJSONSyntaxError 100%900 100%20 100%
httpie\cli\nested_json\errors.py(no class) 100%500 100%00 100%
httpie\cli\nested_json\interpret.py(no class) 96%7730 89%384 94%
httpie\cli\nested_json\parse.py(no class) 95%10750 90%424 94%
httpie\cli\nested_json\tokens.pyTokenKind 100%400 100%40 100%
httpie\cli\nested_json\tokens.pyToken 100%000 100%00 100%
httpie\cli\nested_json\tokens.pyPathAction 100%100 100%00 100%
httpie\cli\nested_json\tokens.pyPath 67%1240 38%81 55%
httpie\cli\nested_json\tokens.pyNestedJSONArray 100%000 100%00 100%
httpie\cli\nested_json\tokens.py(no class) 100%3200 100%00 100%
httpie\cli\options.pyQualifiers 100%000 100%00 100%
httpie\cli\options.pyParserSpec 100%1100 100%60 100%
httpie\cli\options.pyGroup 100%700 100%20 100%
httpie\cli\options.pyArgument 92%3730 94%161 92%
httpie\cli\options.py(no class) 100%6900 100%60 100%
httpie\cli\requestitems.pyRequestItems 100%2500 100%100 100%
httpie\cli\requestitems.py(no class) 89%7580 100%60 90%
httpie\cli\utils.pyManual 33%320 100%00 33%
httpie\cli\utils.pyLazyChoices 100%2200 100%60 100%
httpie\cli\utils.py(no class) 100%1600 100%00 100%
httpie\client.py(no class) 100%17200 96%683 99%
httpie\compat.py(no class) 45%56310 28%185 41%
httpie\config.pyConfigFileError 100%000 100%00 100%
httpie\config.pyBaseConfigDict 95%2210 100%80 97%
httpie\config.pyConfig 100%800 100%00 100%
httpie\config.py(no class) 89%6570 50%61 86%
httpie\context.pyLogLevel 100%000 100%00 100%
httpie\context.pyEnvironment 94%5430 78%184 90%
httpie\context.py(no class) 91%6662 25%41 87%
httpie\cookies.pyHTTPieCookiePolicy 100%500 100%20 100%
httpie\cookies.py(no class) 100%600 100%00 100%
httpie\core.py(no class) 95%17080 85%629 93%
httpie\downloads.pyContentRangeError 100%000 100%00 100%
httpie\downloads.pyDownloader 95%4320 92%121 95%
httpie\downloads.pyDownloadStatus 91%3330 60%104 84%
httpie\downloads.py(no class) 98%9920 87%304 95%
httpie\encoding.py(no class) 100%1800 83%61 96%
httpie\internal\__build_channel__.py(no class) 100%100 100%00 100%
httpie\internal\__init__.py(no class) 100%000 100%00 100%
httpie\internal\daemon_runner.py(no class) 86%2840 100%00 86%
httpie\internal\daemons.py(no class) 57%58250 25%123 51%
httpie\internal\update_warnings.py(no class) 95%9650 75%244 91%
httpie\legacy\__init__.py(no class) 100%000 100%00 100%
httpie\legacy\v3_1_0_session_cookie_format.py(no class) 100%3102 94%161 98%
httpie\legacy\v3_2_0_session_header_format.py(no class) 100%2502 100%100 100%
httpie\manager\__init__.py(no class) 100%000 100%00 100%
httpie\manager\__main__.py(no class) 85%3352 100%40 86%
httpie\manager\cli.py(no class) 100%3900 100%80 100%
httpie\manager\compat.pyPipError 100%200 100%00 100%
httpie\manager\compat.py(no class) 61%33130 12%81 51%
httpie\manager\core.py(no class) 95%2010 88%81 93%
httpie\manager\tasks\__init__.py(no class) 100%500 100%00 100%
httpie\manager\tasks\check_updates.py(no class) 100%800 100%00 100%
httpie\manager\tasks\export_args.py(no class) 93%1410 50%21 88%
httpie\manager\tasks\plugins.pyPluginInstaller 74%118310 52%6011 66%
httpie\manager\tasks\plugins.py(no class) 100%3500 100%00 100%
httpie\manager\tasks\sessions.py(no class) 95%4320 88%162 93%
httpie\models.pyHTTPMessage 64%1140 100%20 69%
httpie\models.pyHTTPResponse 100%2500 100%20 100%
httpie\models.pyHTTPRequest 100%1500 100%40 100%
httpie\models.pyRequestsMessageKind 100%000 100%00 100%
httpie\models.pyOutputOptions 100%500 100%00 100%
httpie\models.py(no class) 98%5710 75%41 97%
httpie\output\__init__.py(no class) 100%000 100%00 100%
httpie\output\formatters\__init__.py(no class) 100%000 100%00 100%
httpie\output\formatters\colors.pyColorFormatter 100%3600 88%81 98%
httpie\output\formatters\colors.pySolarized256Style 100%000 100%00 100%
httpie\output\formatters\colors.py(no class) 100%10400 100%140 100%
httpie\output\formatters\headers.pyHeadersFormatter 100%500 100%00 100%
httpie\output\formatters\headers.py(no class) 100%400 100%00 100%
httpie\output\formatters\json.pyJSONFormatter 100%1100 100%20 100%
httpie\output\formatters\json.py(no class) 100%500 100%00 100%
httpie\output\formatters\xml.pyXMLFormatter 100%1500 100%20 100%
httpie\output\formatters\xml.py(no class) 100%2602 75%82 94%
httpie\output\lexers\__init__.py(no class) 100%000 100%00 100%
httpie\output\lexers\common.py(no class) 100%400 100%20 100%
httpie\output\lexers\http.pySimplifiedHTTPLexer 100%000 100%00 100%
httpie\output\lexers\http.py(no class) 86%2230 50%21 83%
httpie\output\lexers\json.pyEnhancedJsonLexer 100%000 100%00 100%
httpie\output\lexers\json.py(no class) 100%1000 100%00 100%
httpie\output\lexers\metadata.pyMetadataLexer 100%000 100%00 100%
httpie\output\lexers\metadata.py(no class) 82%1730 50%42 76%
httpie\output\models.pyProcessingOptions 83%610 100%20 88%
httpie\output\models.py(no class) 100%2100 100%00 100%
httpie\output\processing.pyConversion 100%400 67%62 80%
httpie\output\processing.pyFormatting 100%1700 100%140 100%
httpie\output\processing.py(no class) 100%1600 100%00 100%
httpie\output\streams.pyDataSuppressedError 100%000 100%00 100%
httpie\output\streams.pyBinarySuppressedError 100%000 100%00 100%
httpie\output\streams.pyBaseStream 96%2510 100%140 97%
httpie\output\streams.pyRawStream 100%300 100%00 100%
httpie\output\streams.pyEncodedStream 96%2710 100%140 98%
httpie\output\streams.pyPrettyStream 96%2710 83%122 92%
httpie\output\streams.pyBufferedPrettyStream 100%1100 100%80 100%
httpie\output\streams.py(no class) 100%4500 100%00 100%
httpie\output\ui\__init__.py(no class) 100%000 100%00 100%
httpie\output\ui\man_pages.py(no class) 0%16160 0%20 0%
httpie\output\ui\palette.pyStyles 100%000 100%00 100%
httpie\output\ui\palette.pyPieStyle 100%000 100%00 100%
httpie\output\ui\palette.pyColorString 50%840 50%61 50%
httpie\output\ui\palette.pyPieColor 100%000 100%00 100%
httpie\output\ui\palette.pyGenericColor 50%630 50%21 50%
httpie\output\ui\palette.py_StyledGenericColor 100%000 100%00 100%
httpie\output\ui\palette.py(no class) 97%5820 75%41 95%
httpie\output\ui\rich_help.pyOptionsHighlighter 100%000 100%00 100%
httpie\output\ui\rich_help.py(no class) 53%104490 31%320 48%
httpie\output\ui\rich_palette.py_GenericColorCaster 80%510 100%20 86%
httpie\output\ui\rich_palette.py(no class) 96%2612 100%40 97%
httpie\output\ui\rich_progress.pyBaseDisplay 85%1323 50%42 76%
httpie\output\ui\rich_progress.pyDummyDisplay 100%000 100%00 100%
httpie\output\ui\rich_progress.pyStatusDisplay 0%12120 0%20 0%
httpie\output\ui\rich_progress.pyProgressDisplay 100%1100 100%20 100%
httpie\output\ui\rich_progress.py(no class) 100%18010 100%00 100%
httpie\output\ui\rich_utils.py(no class) 0%18180 100%00 0%
httpie\output\utils.py(no class) 100%2000 100%00 100%
httpie\output\writer.py(no class) 83%76130 70%301 79%
httpie\plugins\__init__.py(no class) 100%200 100%00 100%
httpie\plugins\base.pyBasePlugin 100%000 100%00 100%
httpie\plugins\base.pyAuthPlugin 0%110 100%00 0%
httpie\plugins\base.pyTransportPlugin 0%110 100%00 0%
httpie\plugins\base.pyConverterPlugin 33%320 100%00 33%
httpie\plugins\base.pyFormatterPlugin 100%600 100%00 100%
httpie\plugins\base.py(no class) 100%2700 100%00 100%
httpie\plugins\builtin.pyBuiltinAuthPlugin 100%000 100%00 100%
httpie\plugins\builtin.pyHTTPBasicAuth 100%500 100%00 100%
httpie\plugins\builtin.pyHTTPBearerAuth 100%300 100%00 100%
httpie\plugins\builtin.pyBasicAuthPlugin 100%100 100%00 100%
httpie\plugins\builtin.pyDigestAuthPlugin 100%100 100%00 100%
httpie\plugins\builtin.pyBearerAuthPlugin 100%100 100%00 100%
httpie\plugins\builtin.py(no class) 100%2800 100%00 100%
httpie\plugins\manager.pyPluginManager 100%2600 100%60 100%
httpie\plugins\manager.py(no class) 98%4110 75%41 96%
httpie\plugins\registry.py(no class) 100%800 100%00 100%
httpie\sessions.pySession 98%8320 98%401 98%
httpie\sessions.py(no class) 99%7210 83%61 97%
httpie\ssl_.pyHTTPieCertificate 100%100 100%00 100%
httpie\ssl_.pyHTTPieHTTPSAdapter 86%1420 100%20 88%
httpie\ssl_.py(no class) 100%2800 100%40 100%
httpie\status.pyExitStatus 100%000 100%00 100%
httpie\status.py(no class) 100%2000 100%60 100%
httpie\uploads.pyChunkedStream 0%110 100%00 0%
httpie\uploads.pyChunkedUploadStream 100%800 100%40 100%
httpie\uploads.pyChunkedMultipartUploadStream 100%900 75%41 92%
httpie\uploads.py(no class) 85%112172 75%443 82%
httpie\utils.pyJsonDictPreservingDuplicateKeys 100%500 100%20 100%
httpie\utils.pyExplicitNullAuth 100%100 100%00 100%
httpie\utils.pyLockFileError 100%000 100%00 100%
httpie\utils.py(no class) 88%125150 66%323 83%
Total  91%418739030 81%1126111 89%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/coverage_html_cb_c6c441fc.js b/courseProjectDocs/Setup/testCoverage/coverage_html_cb_c6c441fc.js new file mode 100644 index 0000000000..18b6343055 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/coverage_html_cb_c6c441fc.js @@ -0,0 +1,738 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// General helpers +function debounce(callback, wait) { + let timeoutId = null; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + callback.apply(this, args); + }, wait); + }; +}; + +function checkVisible(element) { + const rect = element.getBoundingClientRect(); + const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); + const viewTop = 30; + return !(rect.bottom < viewTop || rect.top >= viewBottom); +} + +function on_click(sel, fn) { + const elt = document.querySelector(sel); + if (elt) { + elt.addEventListener("click", fn); + } +} + +// Helpers for table sorting +function getCellValue(row, column = 0) { + const cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.childElementCount == 1) { + var child = cell.firstElementChild; + if (child.tagName === "A") { + child = child.firstElementChild; + } + if (child instanceof HTMLDataElement && child.value) { + return child.value; + } + } + return cell.innerText || cell.textContent; +} + +function rowComparator(rowA, rowB, column = 0) { + let valueA = getCellValue(rowA, column); + let valueB = getCellValue(rowB, column); + if (!isNaN(valueA) && !isNaN(valueB)) { + return valueA - valueB; + } + return valueA.localeCompare(valueB, undefined, {numeric: true}); +} + +function sortColumn(th) { + // Get the current sorting direction of the selected header, + // clear state on other headers and then set the new sorting direction. + const currentSortOrder = th.getAttribute("aria-sort"); + [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + var direction; + if (currentSortOrder === "none") { + direction = th.dataset.defaultSortOrder || "ascending"; + } + else if (currentSortOrder === "ascending") { + direction = "descending"; + } + else { + direction = "ascending"; + } + th.setAttribute("aria-sort", direction); + + const column = [...th.parentElement.cells].indexOf(th) + + // Sort all rows and afterwards append them in order to move them in the DOM. + Array.from(th.closest("table").querySelectorAll("tbody tr")) + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (direction === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr)); + + // Save the sort order for next time. + if (th.id !== "region") { + let th_id = "file"; // Sort by file if we don't have a column id + let current_direction = direction; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)) + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + "th_id": th.id, + "direction": current_direction + })); + if (th.id !== th_id || document.getElementById("region")) { + // Sort column has changed, unset sorting by function or class. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": false, + "region_direction": current_direction + })); + } + } + else { + // Sort column has changed to by function or class, remember that. + localStorage.setItem(coverage.SORTED_BY_REGION, JSON.stringify({ + "by_region": true, + "region_direction": direction + })); + } +} + +// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + document.querySelectorAll("[data-shortcut]").forEach(element => { + document.addEventListener("keypress", event => { + if (event.target.tagName.toLowerCase() === "input") { + return; // ignore keypress from search filter + } + if (event.key === element.dataset.shortcut) { + element.click(); + } + }); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Populate the filter and hide100 inputs if there are saved values for them. + const saved_filter_value = localStorage.getItem(coverage.FILTER_STORAGE); + if (saved_filter_value) { + document.getElementById("filter").value = saved_filter_value; + } + const saved_hide100_value = localStorage.getItem(coverage.HIDE100_STORAGE); + if (saved_hide100_value) { + document.getElementById("hide100").checked = JSON.parse(saved_hide100_value); + } + + // Cache elements. + const table = document.querySelector("table.index"); + const table_body_rows = table.querySelectorAll("tbody tr"); + const no_rows = document.getElementById("no_rows"); + + const footer = table.tFoot.rows[0]; + const ratio_columns = Array.from(footer.cells).map(cell => Boolean(cell.dataset.ratio)); + + // Observe filter keyevents. + const filter_handler = (event => { + // Keep running total of each metric, first index contains number of shown rows + const totals = ratio_columns.map( + is_ratio => is_ratio ? {"numer": 0, "denom": 0} : 0 + ); + + var text = document.getElementById("filter").value; + // Store filter value + localStorage.setItem(coverage.FILTER_STORAGE, text); + const casefold = (text === text.toLowerCase()); + const hide100 = document.getElementById("hide100").checked; + // Store hide value. + localStorage.setItem(coverage.HIDE100_STORAGE, JSON.stringify(hide100)); + + // Hide / show elements. + table_body_rows.forEach(row => { + var show = false; + // Check the text filter. + for (let column = 0; column < totals.length; column++) { + cell = row.cells[column]; + if (cell.classList.contains("name")) { + var celltext = cell.textContent; + if (casefold) { + celltext = celltext.toLowerCase(); + } + if (celltext.includes(text)) { + show = true; + } + } + } + + // Check the "hide covered" filter. + if (show && hide100) { + const [numer, denom] = row.cells[row.cells.length - 1].dataset.ratio.split(" "); + show = (numer !== denom); + } + + if (!show) { + // hide + row.classList.add("hidden"); + return; + } + + // show + row.classList.remove("hidden"); + totals[0]++; + + for (let column = 0; column < totals.length; column++) { + // Accumulate dynamic totals + cell = row.cells[column] // nosemgrep: eslint.detect-object-injection + if (cell.matches(".name, .spacer")) { + continue; + } + if (ratio_columns[column] && cell.dataset.ratio) { + // Column stores a ratio + const [numer, denom] = cell.dataset.ratio.split(" "); + totals[column]["numer"] += parseInt(numer, 10); // nosemgrep: eslint.detect-object-injection + totals[column]["denom"] += parseInt(denom, 10); // nosemgrep: eslint.detect-object-injection + } + else { + totals[column] += parseInt(cell.textContent, 10); // nosemgrep: eslint.detect-object-injection + } + } + }); + + // Show placeholder if no rows will be displayed. + if (!totals[0]) { + // Show placeholder, hide table. + no_rows.style.display = "block"; + table.style.display = "none"; + return; + } + + // Hide placeholder, show table. + no_rows.style.display = null; + table.style.display = null; + + // Calculate new dynamic sum values based on visible rows. + for (let column = 0; column < totals.length; column++) { + // Get footer cell element. + const cell = footer.cells[column]; // nosemgrep: eslint.detect-object-injection + if (cell.matches(".name, .spacer")) { + continue; + } + + // Set value into dynamic footer cell element. + if (ratio_columns[column]) { + // Percentage column uses the numerator and denominator, + // and adapts to the number of decimal places. + const match = /\.([0-9]+)/.exec(cell.textContent); + const places = match ? match[1].length : 0; + const { numer, denom } = totals[column]; // nosemgrep: eslint.detect-object-injection + cell.dataset.ratio = `${numer} ${denom}`; + // Check denom to prevent NaN if filtered files contain no statements + cell.textContent = denom + ? `${(numer * 100 / denom).toFixed(places)}%` + : `${(100).toFixed(places)}%`; + } + else { + cell.textContent = totals[column]; // nosemgrep: eslint.detect-object-injection + } + } + }); + + document.getElementById("filter").addEventListener("input", debounce(filter_handler)); + document.getElementById("hide100").addEventListener("input", debounce(filter_handler)); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + document.getElementById("filter").dispatchEvent(new Event("input")); + document.getElementById("hide100").dispatchEvent(new Event("input")); +}; +coverage.FILTER_STORAGE = "COVERAGE_FILTER_VALUE"; +coverage.HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE"; + +// Set up the click-to-sort columns. +coverage.wire_up_sorting = function () { + document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( + th => th.addEventListener("click", e => sortColumn(e.target)) + ); + + // Look for a localStorage item containing previous sort settings: + let th_id = "file", direction = "ascending"; + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + if (stored_list) { + ({th_id, direction} = JSON.parse(stored_list)); + } + let by_region = false, region_direction = "ascending"; + const sorted_by_region = localStorage.getItem(coverage.SORTED_BY_REGION); + if (sorted_by_region) { + ({ + by_region, + region_direction + } = JSON.parse(sorted_by_region)); + } + + const region_id = "region"; + if (by_region && document.getElementById(region_id)) { + direction = region_direction; + } + // If we are in a page that has a column with id of "region", sort on + // it if the last sort was by function or class. + let th; + if (document.getElementById(region_id)) { + th = document.getElementById(by_region ? region_id : th_id); + } + else { + th = document.getElementById(th_id); + } + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; +coverage.SORTED_BY_REGION = "COVERAGE_SORT_REGION"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + coverage.wire_up_sorting(); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + + on_click(".button_show_hide_help", coverage.show_hide_help); +}; + +// -- pyfile stuff -- + +coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; + +coverage.pyfile_ready = function () { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === "t") { + document.querySelector(frag).closest(".n").classList.add("highlight"); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + on_click(".button_toggle_run", coverage.toggle_lines); + on_click(".button_toggle_mis", coverage.toggle_lines); + on_click(".button_toggle_exc", coverage.toggle_lines); + on_click(".button_toggle_par", coverage.toggle_lines); + + on_click(".button_next_chunk", coverage.to_next_chunk_nicely); + on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); + on_click(".button_top_of_page", coverage.to_top); + on_click(".button_first_chunk", coverage.to_first_chunk); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + on_click(".button_to_index", coverage.to_index); + + on_click(".button_show_hide_help", coverage.show_hide_help); + + coverage.filters = undefined; + try { + coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); + } catch(err) {} + + if (coverage.filters) { + coverage.filters = JSON.parse(coverage.filters); + } + else { + coverage.filters = {run: false, exc: true, mis: true, par: true}; + } + + for (cls in coverage.filters) { + coverage.set_line_visibilty(cls, coverage.filters[cls]); // nosemgrep: eslint.detect-object-injection + } + + coverage.assign_shortkeys(); + coverage.init_scroll_markers(); + coverage.wire_up_sticky_header(); + + document.querySelectorAll("[id^=ctxs]").forEach( + cbox => cbox.addEventListener("click", coverage.expand_contexts) + ); + + // Rebuild scroll markers when the window height changes. + window.addEventListener("resize", coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (event) { + const btn = event.target.closest("button"); + const category = btn.value + const show = !btn.classList.contains("show_" + category); + coverage.set_line_visibilty(category, show); + coverage.build_scroll_markers(); + coverage.filters[category] = show; + try { + localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); + } catch(err) {} +}; + +coverage.set_line_visibilty = function (category, should_show) { + const cls = "show_" + category; + const btn = document.querySelector(".button_toggle_" + category); + if (btn) { + if (should_show) { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); + btn.classList.add(cls); + } + else { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); + btn.classList.remove(cls); + } + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return document.getElementById("t" + n)?.closest("p"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.to_prev_file = function () { + window.location = document.getElementById("prevFileLink").href; +} + +coverage.to_next_file = function () { + window.location = document.getElementById("nextFileLink").href; +} + +coverage.to_index = function () { + location.href = document.getElementById("indexLink").href; +} + +coverage.show_hide_help = function () { + const helpCheck = document.getElementById("help_panel_state") + helpCheck.checked = !helpCheck.checked; +} + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + const classes = line_elt?.className; + if (!classes) { + return null; + } + const match = classes.match(/\bshow_\w+\b/); + if (!match) { + return null; + } + return match[0]; +}; + +coverage.to_next_chunk = function () { + const c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + const c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 1 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There is no previous highlighted chunk. + if (!chunk_indicator) { + return; + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + while (probe > 1) { + probe_line = c.line_elt(probe-1); + if (c.chunk_indicator(probe_line) !== chunk_indicator) { + break; + } + probe--; + } + c.set_sel(probe, end); + c.show_selection(); +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + const begin = coverage.line_elt(coverage.sel_begin); + const end = coverage.line_elt(coverage.sel_end-1); + + return ( + (checkVisible(begin) ? 1 : 0) + + (checkVisible(end) ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the top line on the screen as selection. + + // This will select the top-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(0, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(1); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the lowest line on the screen as selection. + + // This will select the bottom-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(coverage.lines_len); + } + else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (!probe_line) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + // Highlight the lines in the chunk + document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); + for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { + coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); + } + + coverage.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + const element = coverage.line_elt(coverage.sel_begin); + coverage.scroll_window(element.offsetTop - 60); + } +}; + +coverage.scroll_window = function (to_pos) { + window.scroll({top: to_pos, behavior: "smooth"}); +}; + +coverage.init_scroll_markers = function () { + // Init some variables + coverage.lines_len = document.querySelectorAll("#source > p").length; + + // Build html + coverage.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + const temp_scroll_marker = document.getElementById("scroll_marker") + if (temp_scroll_marker) temp_scroll_marker.remove(); + // Don't build markers if the window has no scroll bar. + if (document.body.scrollHeight <= window.innerHeight) { + return; + } + + const marker_scale = window.innerHeight / document.body.scrollHeight; + const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); + + let previous_line = -99, last_mark, last_top; + + const scroll_marker = document.createElement("div"); + scroll_marker.id = "scroll_marker"; + document.getElementById("source").querySelectorAll( + "p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par" + ).forEach(element => { + const line_top = Math.floor(element.offsetTop * marker_scale); + const line_number = parseInt(element.querySelector(".n a").id.substr(1)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.style.height = `${line_top + line_height - last_top}px`; + } + else { + // Add colored line in scroll_marker block. + last_mark = document.createElement("div"); + last_mark.id = `m${line_number}`; + last_mark.classList.add("marker"); + last_mark.style.height = `${line_height}px`; + last_mark.style.top = `${line_top}px`; + scroll_marker.append(last_mark); + last_top = line_top; + } + + previous_line = line_number; + }); + + // Append last to prevent layout calculation + document.body.append(scroll_marker); +}; + +coverage.wire_up_sticky_header = function () { + const header = document.querySelector("header"); + const header_bottom = ( + header.querySelector(".content h2").getBoundingClientRect().top - + header.getBoundingClientRect().top + ); + + function updateHeader() { + if (window.scrollY > header_bottom) { + header.classList.add("sticky"); + } + else { + header.classList.remove("sticky"); + } + } + + window.addEventListener("scroll", updateHeader); + updateHeader(); +}; + +coverage.expand_contexts = function (e) { + var ctxs = e.target.parentNode.querySelector(".ctxs"); + + if (!ctxs.classList.contains("expanded")) { + var ctxs_text = ctxs.textContent; + var width = Number(ctxs_text[0]); + ctxs.textContent = ""; + for (var i = 1; i < ctxs_text.length; i += width) { + key = ctxs_text.substring(i, i + width).trim(); + ctxs.appendChild(document.createTextNode(contexts[key])); + ctxs.appendChild(document.createElement("br")); + } + ctxs.classList.add("expanded"); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + if (document.body.classList.contains("indexfile")) { + coverage.index_ready(); + } + else { + coverage.pyfile_ready(); + } +}); diff --git a/courseProjectDocs/Setup/testCoverage/favicon_32_cb_c827f16f.png b/courseProjectDocs/Setup/testCoverage/favicon_32_cb_c827f16f.png new file mode 100644 index 0000000000..8649f0475d Binary files /dev/null and b/courseProjectDocs/Setup/testCoverage/favicon_32_cb_c827f16f.png differ diff --git a/courseProjectDocs/Setup/testCoverage/function_index.html b/courseProjectDocs/Setup/testCoverage/function_index.html new file mode 100644 index 0000000000..453d37b5c3 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/function_index.html @@ -0,0 +1,8158 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 89% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.16.2, + created at 2026-09-28 11:59 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   Statements Branches Total
Filefunction coveragestatementsmissingexcluded coveragebranchespartial coverage
httpie\__init__.py(no function) 100%400 100%00 100%
httpie\__main__.pymain 100%700 100%00 100%
httpie\__main__.py(no function) 100%103 100%00 100%
httpie\adapters.pyHTTPieHTTPAdapter.build_response 100%300 100%00 100%
httpie\adapters.py(no function) 100%400 100%00 100%
httpie\cli\__init__.py(no function) 100%000 100%00 100%
httpie\cli\argparser.pyHTTPieHelpFormatter.__init__ 100%200 100%00 100%
httpie\cli\argparser.pyHTTPieHelpFormatter._split_lines 100%200 100%00 100%
httpie\cli\argparser.pyHTTPieHelpFormatter.add_usage 80%510 50%21 71%
httpie\cli\argparser.pyBaseHTTPieArgumentParser.__init__ 100%500 100%00 100%
httpie\cli\argparser.pyBaseHTTPieArgumentParser.parse_args 100%700 100%20 100%
httpie\cli\argparser.pyBaseHTTPieArgumentParser._print_message 88%810 67%62 79%
httpie\cli\argparser.pyHTTPieManagerArgumentParser.parse_known_args 100%600 100%20 100%
httpie\cli\argparser.pyHTTPieArgumentParser.__init__ 100%200 100%00 100%
httpie\cli\argparser.pyHTTPieArgumentParser.parse_args 100%2900 100%120 100%
httpie\cli\argparser.pyHTTPieArgumentParser._process_request_type 100%400 100%00 100%
httpie\cli\argparser.pyHTTPieArgumentParser._process_url 100%1500 100%100 100%
httpie\cli\argparser.pyHTTPieArgumentParser._setup_standard_streams 82%2240 83%120 82%
httpie\cli\argparser.pyHTTPieArgumentParser._process_ssl_cert 100%500 100%40 100%
httpie\cli\argparser.pyHTTPieArgumentParser._process_auth 100%3600 100%260 100%
httpie\cli\argparser.pyHTTPieArgumentParser._apply_no_options 85%1320 90%101 87%
httpie\cli\argparser.pyHTTPieArgumentParser._body_from_file 100%200 100%00 100%
httpie\cli\argparser.pyHTTPieArgumentParser._body_from_input 100%200 100%00 100%
httpie\cli\argparser.pyHTTPieArgumentParser._ensure_one_data_source 100%200 100%20 100%
httpie\cli\argparser.pyHTTPieArgumentParser._guess_method 93%1510 88%81 91%
httpie\cli\argparser.pyHTTPieArgumentParser._parse_items 96%2610 86%142 92%
httpie\cli\argparser.pyHTTPieArgumentParser._process_output_options 100%1900 94%161 97%
httpie\cli\argparser.pyHTTPieArgumentParser._process_output_options.check_options 67%310 50%21 60%
httpie\cli\argparser.pyHTTPieArgumentParser._process_pretty_options 100%500 100%40 100%
httpie\cli\argparser.pyHTTPieArgumentParser._process_download_options 78%920 75%82 76%
httpie\cli\argparser.pyHTTPieArgumentParser._process_format_options 100%500 100%20 100%
httpie\cli\argparser.pyHTTPieArgumentParser.print_manual 0%770 0%20 0%
httpie\cli\argparser.pyHTTPieArgumentParser.print_usage 100%1000 100%20 100%
httpie\cli\argparser.pyHTTPieArgumentParser.error 100%300 100%00 100%
httpie\cli\argparser.py(no function) 100%4700 100%00 100%
httpie\cli\argtypes.pyKeyValueArg.__init__ 100%400 100%00 100%
httpie\cli\argtypes.pyKeyValueArg.__eq__ 100%100 100%00 100%
httpie\cli\argtypes.pyKeyValueArg.__repr__ 0%110 100%00 0%
httpie\cli\argtypes.pySessionNameValidator.__init__ 100%100 100%00 100%
httpie\cli\argtypes.pySessionNameValidator.__call__ 67%310 50%21 60%
httpie\cli\argtypes.pyEscaped.__repr__ 0%110 100%00 0%
httpie\cli\argtypes.pyKeyValueArgType.__init__ 100%400 100%20 100%
httpie\cli\argtypes.pyKeyValueArgType.__call__ 100%1800 100%100 100%
httpie\cli\argtypes.pyKeyValueArgType.tokenize 100%1000 100%60 100%
httpie\cli\argtypes.pyPromptMixin._prompt_password 50%630 100%00 50%
httpie\cli\argtypes.pyPromptMixin._getpass 0%110 100%00 0%
httpie\cli\argtypes.pySSLCredentials.__init__ 100%100 100%00 100%
httpie\cli\argtypes.pySSLCredentials.prompt_password 100%100 100%00 100%
httpie\cli\argtypes.pyAuthCredentials.has_password 100%100 100%00 100%
httpie\cli\argtypes.pyAuthCredentials.prompt_password 100%100 100%00 100%
httpie\cli\argtypes.pyAuthCredentialsArgType.__call__ 100%400 100%00 100%
httpie\cli\argtypes.pyreadable_file_arg 100%500 100%00 100%
httpie\cli\argtypes.pyparse_format_options 100%2400 100%100 100%
httpie\cli\argtypes.pyresponse_charset_type 100%500 100%00 100%
httpie\cli\argtypes.pyresponse_mime_type 100%300 100%20 100%
httpie\cli\argtypes.py(no function) 100%4100 100%00 100%
httpie\cli\constants.py(no function) 100%4600 100%00 100%
httpie\cli\definition.pyformat_style_help 60%1040 50%42 57%
httpie\cli\definition.pyformat_auth_help 56%940 50%21 55%
httpie\cli\definition.py(no function) 100%8900 100%00 100%
httpie\cli\dicts.pyHTTPHeadersDict.add 100%600 100%40 100%
httpie\cli\dicts.pyHTTPHeadersDict.remove_item 75%410 50%21 67%
httpie\cli\dicts.pyMultiValueOrderedDict.__setitem__ 100%600 75%41 90%
httpie\cli\dicts.pyMultiValueOrderedDict.items 100%500 100%60 100%
httpie\cli\dicts.py(no function) 100%1900 100%00 100%
httpie\cli\exceptions.py(no function) 100%200 100%00 100%
httpie\cli\nested_json\__init__.py(no function) 100%400 100%00 100%
httpie\cli\nested_json\errors.pyNestedJSONSyntaxError.__init__ 100%400 100%00 100%
httpie\cli\nested_json\errors.pyNestedJSONSyntaxError.__str__ 100%500 100%20 100%
httpie\cli\nested_json\errors.py(no function) 100%500 100%00 100%
httpie\cli\nested_json\interpret.pyinterpret_nested_json 100%400 100%20 100%
httpie\cli\nested_json\interpret.pyinterpret 97%3410 90%202 94%
httpie\cli\nested_json\interpret.pyinterpret.type_check 100%1100 100%40 100%
httpie\cli\nested_json\interpret.pyinterpret.object_for 80%510 75%41 78%
httpie\cli\nested_json\interpret.pywrap_with_dict 83%610 75%41 80%
httpie\cli\nested_json\interpret.pyunwrap_top_level_list_if_needed 100%600 100%40 100%
httpie\cli\nested_json\interpret.py(no function) 100%1100 100%00 100%
httpie\cli\nested_json\parse.pyparse 95%2010 88%81 93%
httpie\cli\nested_json\parse.pyparse.can_advance 100%100 100%00 100%
httpie\cli\nested_json\parse.pyparse.expect 93%1510 88%81 91%
httpie\cli\nested_json\parse.pyparse.parse_root 90%2120 80%102 87%
httpie\cli\nested_json\parse.pytokenize 100%1900 100%80 100%
httpie\cli\nested_json\parse.pytokenize.send_buffer 100%1400 100%60 100%
httpie\cli\nested_json\parse.pytokenize.can_advance 100%100 100%00 100%
httpie\cli\nested_json\parse.pycheck_escaped_int 100%700 100%20 100%
httpie\cli\nested_json\parse.pyassert_cant_happen 0%110 100%00 0%
httpie\cli\nested_json\parse.py(no function) 100%800 100%00 100%
httpie\cli\nested_json\tokens.pyTokenKind.to_name 100%400 100%40 100%
httpie\cli\nested_json\tokens.pyPathAction.to_string 100%100 100%00 100%
httpie\cli\nested_json\tokens.pyPath.__init__ 100%400 100%00 100%
httpie\cli\nested_json\tokens.pyPath.reconstruct 50%840 38%81 44%
httpie\cli\nested_json\tokens.py(no function) 100%3200 100%00 100%
httpie\cli\options.pymap_qualifiers 100%100 100%00 100%
httpie\cli\options.pydrop_keys 100%100 100%00 100%
httpie\cli\options.pyParserSpec.finalize 100%700 100%60 100%
httpie\cli\options.pyParserSpec.add_group 100%300 100%00 100%
httpie\cli\options.pyParserSpec.serialize 100%100 100%00 100%
httpie\cli\options.pyGroup.finalize 100%200 100%20 100%
httpie\cli\options.pyGroup.add_argument 100%400 100%00 100%
httpie\cli\options.pyGroup.serialize 100%100 100%00 100%
httpie\cli\options.pyArgument.post_init 100%300 100%20 100%
httpie\cli\options.pyArgument.serialize 100%2900 100%120 100%
httpie\cli\options.pyArgument.is_positional 0%110 100%00 0%
httpie\cli\options.pyArgument.is_hidden 0%110 100%00 0%
httpie\cli\options.pyArgument.__getattr__ 67%310 50%21 60%
httpie\cli\options.pyto_argparse 100%1100 100%60 100%
httpie\cli\options.pyto_data 100%100 100%00 100%
httpie\cli\options.pyparser_to_parser_spec 100%100 100%00 100%
httpie\cli\options.py(no function) 100%5400 100%00 100%
httpie\cli\requestitems.pyRequestItems.__init__ 100%700 100%00 100%
httpie\cli\requestitems.pyRequestItems.from_args 100%1800 100%100 100%
httpie\cli\requestitems.pyprocess_header_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_embed_header_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_empty_header_arg 100%300 100%20 100%
httpie\cli\requestitems.pyprocess_query_param_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_embed_query_param_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_file_upload_arg 100%800 100%00 100%
httpie\cli\requestitems.pyconvert_json_value_to_form_if_needed 100%500 100%20 100%
httpie\cli\requestitems.pyconvert_json_value_to_form_if_needed.wrapper 71%720 100%20 78%
httpie\cli\requestitems.pyprocess_data_item_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_data_embed_file_contents_arg 100%100 100%00 100%
httpie\cli\requestitems.pyprocess_data_embed_raw_json_file_arg 100%300 100%00 100%
httpie\cli\requestitems.pyprocess_data_raw_json_embed_arg 100%200 100%00 100%
httpie\cli\requestitems.pyprocess_data_nested_json_embed_args 100%100 100%00 100%
httpie\cli\requestitems.pyload_text_file 50%840 100%00 50%
httpie\cli\requestitems.pyload_json 50%420 100%00 50%
httpie\cli\requestitems.py(no function) 100%2800 100%00 100%
httpie\cli\utils.pyManual.__init__ 100%100 100%00 100%
httpie\cli\utils.pyManual.__call__ 0%220 100%00 0%
httpie\cli\utils.pyLazyChoices.__init__ 100%900 100%00 100%
httpie\cli\utils.pyLazyChoices.load 100%400 100%20 100%
httpie\cli\utils.pyLazyChoices.help 100%300 100%20 100%
httpie\cli\utils.pyLazyChoices.help 100%100 100%00 100%
httpie\cli\utils.pyLazyChoices.__contains__ 100%100 100%00 100%
httpie\cli\utils.pyLazyChoices.__iter__ 100%300 100%20 100%
httpie\cli\utils.pyLazyChoices.__call__ 100%100 100%00 100%
httpie\cli\utils.py(no function) 100%1600 100%00 100%
httpie\client.pycollect_messages 100%5100 97%301 99%
httpie\client.pymax_headers 100%500 100%00 100%
httpie\client.pybuild_requests_session 100%900 100%20 100%
httpie\client.pydump_request 100%100 100%00 100%
httpie\client.pyfinalize_headers 100%1000 88%81 94%
httpie\client.pytransform_headers 100%300 100%20 100%
httpie\client.pyapply_missing_repeated_headers 100%1000 100%60 100%
httpie\client.pymake_default_headers 100%900 83%61 93%
httpie\client.pymake_send_kwargs 100%100 100%00 100%
httpie\client.pymake_send_kwargs_mergeable_from_env 100%600 100%40 100%
httpie\client.pyjson_dict_to_request_body 100%500 100%20 100%
httpie\client.pymake_request_kwargs 100%1500 100%80 100%
httpie\client.pyensure_path_as_is 100%300 100%00 100%
httpie\client.py(no function) 100%4400 100%00 100%
httpie\compat.pyfind_entry_points 67%310 50%21 60%
httpie\compat.pyget_dist_name 25%1290 25%41 25%
httpie\compat.pyensure_default_certs_loaded 100%300 50%42 71%
httpie\compat.py(no function) 45%38210 12%81 39%
httpie\config.pyget_default_config_dir 45%1160 50%61 47%
httpie\config.pyread_raw_config 90%1010 100%00 90%
httpie\config.pyBaseConfigDict.__init__ 100%200 100%00 100%
httpie\config.pyBaseConfigDict.ensure_directory 100%100 100%00 100%
httpie\config.pyBaseConfigDict.is_new 100%100 100%00 100%
httpie\config.pyBaseConfigDict.pre_process_data 0%110 100%00 0%
httpie\config.pyBaseConfigDict.post_process_data 100%100 100%00 100%
httpie\config.pyBaseConfigDict.load 100%500 100%20 100%
httpie\config.pyBaseConfigDict.save 100%1000 100%60 100%
httpie\config.pyBaseConfigDict.version 100%100 100%00 100%
httpie\config.pyConfig.__init__ 100%300 100%00 100%
httpie\config.pyConfig.default_options 100%100 100%00 100%
httpie\config.pyConfig._configured_path 100%100 100%00 100%
httpie\config.pyConfig.plugins_dir 100%100 100%00 100%
httpie\config.pyConfig.version_info_file 100%100 100%00 100%
httpie\config.pyConfig.developer_mode 100%100 100%00 100%
httpie\config.py(no function) 100%4400 100%00 100%
httpie\context.pyEnvironment.__init__ 93%1410 62%83 82%
httpie\context.pyEnvironment.__str__ 100%500 100%00 100%
httpie\context.pyEnvironment.__repr__ 100%100 100%00 100%
httpie\context.pyEnvironment.config 100%900 100%40 100%
httpie\context.pyEnvironment.devnull 100%300 100%20 100%
httpie\context.pyEnvironment.as_silent 100%800 100%00 100%
httpie\context.pyEnvironment.log_error 80%510 50%21 71%
httpie\context.pyEnvironment.apply_warnings_filter 100%200 100%20 100%
httpie\context.pyEnvironment._make_rich_console 100%500 100%00 100%
httpie\context.pyEnvironment.rich_console 0%110 100%00 0%
httpie\context.pyEnvironment.rich_error_console 100%100 100%00 100%
httpie\context.py(no function) 91%6662 25%41 87%
httpie\cookies.pyHTTPieCookiePolicy.return_ok_secure 100%400 100%20 100%
httpie\cookies.pyHTTPieCookiePolicy._is_local_host 100%100 100%00 100%
httpie\cookies.py(no function) 100%600 100%00 100%
httpie\core.pyraw_main 93%7050 75%287 88%
httpie\core.pyraw_main.handle_generic_error 100%1000 100%80 100%
httpie\core.pymain 100%200 100%00 100%
httpie\core.pyprogram 94%4930 92%242 93%
httpie\core.pyprogram.separate 100%100 100%00 100%
httpie\core.pyprogram.request_body_read_callback 100%300 100%20 100%
httpie\core.pyprint_debug_info 100%600 100%00 100%
httpie\core.pydecode_raw_args 100%100 100%00 100%
httpie\core.py(no function) 100%2800 100%00 100%
httpie\downloads.pyparse_content_range 100%1500 100%80 100%
httpie\downloads.pyfilename_from_content_disposition 100%600 75%41 90%
httpie\downloads.pyfilename_from_url 92%1210 88%81 90%
httpie\downloads.pytrim_filename 100%700 75%41 91%
httpie\downloads.pyget_filename_max_length 75%410 50%21 67%
httpie\downloads.pytrim_filename_if_needed 100%400 100%20 100%
httpie\downloads.pyget_unique_filename 100%800 100%20 100%
httpie\downloads.pyDownloader.__init__ 100%500 100%00 100%
httpie\downloads.pyDownloader.pre_request 100%600 75%41 90%
httpie\downloads.pyDownloader.start 89%1920 100%40 91%
httpie\downloads.pyDownloader.finish 100%300 100%00 100%
httpie\downloads.pyDownloader.failed 100%100 100%00 100%
httpie\downloads.pyDownloader.interrupted 100%100 100%00 100%
httpie\downloads.pyDownloader.chunk_downloaded 100%100 100%00 100%
httpie\downloads.pyDownloader._get_output_file_from_response 100%700 100%40 100%
httpie\downloads.pyDownloadStatus.__init__ 100%600 100%00 100%
httpie\downloads.pyDownloadStatus.started 100%500 100%00 100%
httpie\downloads.pyDownloadStatus.start_display 88%810 75%41 83%
httpie\downloads.pyDownloadStatus.chunk_downloaded 100%300 100%00 100%
httpie\downloads.pyDownloadStatus.has_finished 0%110 100%00 0%
httpie\downloads.pyDownloadStatus.time_spent 67%310 50%21 60%
httpie\downloads.pyDownloadStatus.finished 100%500 50%21 86%
httpie\downloads.pyDownloadStatus.terminate 100%200 50%21 75%
httpie\downloads.py(no function) 100%4300 100%00 100%
httpie\encoding.pydetect_encoding 100%600 75%41 90%
httpie\encoding.pysmart_decode 100%300 100%20 100%
httpie\encoding.pysmart_encode 100%100 100%00 100%
httpie\encoding.py(no function) 100%800 100%00 100%
httpie\internal\__build_channel__.py(no function) 100%100 100%00 100%
httpie\internal\__init__.py(no function) 100%000 100%00 100%
httpie\internal\daemon_runner.py_check_status 0%440 100%00 0%
httpie\internal\daemon_runner.py_parse_options 100%400 100%00 100%
httpie\internal\daemon_runner.pyis_daemon_mode 100%100 100%00 100%
httpie\internal\daemon_runner.pyrun_daemon_task 100%700 100%00 100%
httpie\internal\daemon_runner.py(no function) 100%1200 100%00 100%
httpie\internal\daemons.py_start_process 100%500 50%21 86%
httpie\internal\daemons.py_spawn_windows 100%500 100%00 100%
httpie\internal\daemons.py_spawn_posix 0%24240 0%60 0%
httpie\internal\daemons.py_spawn 67%310 50%21 60%
httpie\internal\daemons.pyspawn_daemon 100%600 50%21 88%
httpie\internal\daemons.py(no function) 100%1500 100%00 100%
httpie\internal\update_warnings.py_read_data_error_free 100%500 100%00 100%
httpie\internal\update_warnings.py_fetch_updates 100%900 100%00 100%
httpie\internal\update_warnings.pyfetch_updates 67%310 50%21 60%
httpie\internal\update_warnings.pymaybe_fetch_updates 100%1000 83%61 94%
httpie\internal\update_warnings.py_get_suppress_context 67%310 50%21 60%
httpie\internal\update_warnings.py_update_checker 100%200 100%00 100%
httpie\internal\update_warnings.py_update_checker.wrapper 100%400 100%00 100%
httpie\internal\update_warnings.py_get_update_status 100%1500 100%60 100%
httpie\internal\update_warnings.pyget_update_status 100%100 100%00 100%
httpie\internal\update_warnings.pycheck_updates 83%1830 62%81 77%
httpie\internal\update_warnings.py(no function) 100%2600 100%00 100%
httpie\legacy\__init__.py(no function) 100%000 100%00 100%
httpie\legacy\v3_1_0_session_cookie_format.pypre_process 100%1200 100%60 100%
httpie\legacy\v3_1_0_session_cookie_format.pypost_process 100%300 100%20 100%
httpie\legacy\v3_1_0_session_cookie_format.pyfix_layout 100%800 88%81 94%
httpie\legacy\v3_1_0_session_cookie_format.py(no function) 100%802 100%00 100%
httpie\legacy\v3_2_0_session_header_format.pypre_process 100%1100 100%60 100%
httpie\legacy\v3_2_0_session_header_format.pypost_process 100%300 100%20 100%
httpie\legacy\v3_2_0_session_header_format.pyfix_layout 100%400 100%20 100%
httpie\legacy\v3_2_0_session_header_format.py(no function) 100%702 100%00 100%
httpie\manager\__init__.py(no function) 100%000 100%00 100%
httpie\manager\__main__.pyis_http_command 100%1000 100%20 100%
httpie\manager\__main__.pymain 100%800 100%20 100%
httpie\manager\__main__.pyprogram 0%550 100%00 0%
httpie\manager\__main__.py(no function) 100%1002 100%00 100%
httpie\manager\cli.pymissing_subcommand 100%600 100%20 100%
httpie\manager\cli.pygenerate_subparsers 100%1700 100%60 100%
httpie\manager\cli.py(no function) 100%1600 100%00 100%
httpie\manager\compat.pyPipError.__init__ 100%200 100%00 100%
httpie\manager\compat.py_discover_system_pip 0%770 0%40 0%
httpie\manager\compat.py_discover_system_pip._check_pip_version 0%550 0%20 0%
httpie\manager\compat.py_run_pip_subprocess 100%600 100%00 100%
httpie\manager\compat.pyrun_pip 75%410 50%21 67%
httpie\manager\compat.py(no function) 100%1100 100%00 100%
httpie\manager\core.pydispatch_cli_task 100%300 100%20 100%
httpie\manager\core.pyprogram 86%710 83%61 85%
httpie\manager\core.py(no function) 100%1000 100%00 100%
httpie\manager\tasks\__init__.py(no function) 100%500 100%00 100%
httpie\manager\tasks\check_updates.pycli_check_updates 100%300 100%00 100%
httpie\manager\tasks\check_updates.py(no function) 100%500 100%00 100%
httpie\manager\tasks\export_args.pycli_export_args 80%510 50%21 71%
httpie\manager\tasks\export_args.py(no function) 100%900 100%00 100%
httpie\manager\tasks\plugins.pyPluginInstaller.__init__ 100%400 100%00 100%
httpie\manager\tasks\plugins.pyPluginInstaller.setup_plugins_dir 40%530 100%00 40%
httpie\manager\tasks\plugins.pyPluginInstaller.fail 100%700 75%41 91%
httpie\manager\tasks\plugins.pyPluginInstaller._install 93%2720 60%104 84%
httpie\manager\tasks\plugins.pyPluginInstaller.install 100%400 100%00 100%
httpie\manager\tasks\plugins.pyPluginInstaller._clear_metadata 0%14140 0%140 0%
httpie\manager\tasks\plugins.pyPluginInstaller.upgrade 0%990 0%40 0%
httpie\manager\tasks\plugins.pyPluginInstaller._uninstall 88%1720 62%83 80%
httpie\manager\tasks\plugins.pyPluginInstaller.uninstall 100%400 100%20 100%
httpie\manager\tasks\plugins.pyPluginInstaller.list 100%1400 88%81 95%
httpie\manager\tasks\plugins.pyPluginInstaller.run 92%1310 80%102 87%
httpie\manager\tasks\plugins.pycli_plugins 100%600 100%00 100%
httpie\manager\tasks\plugins.py(no function) 100%2900 100%00 100%
httpie\manager\tasks\sessions.pycli_sessions 75%820 67%62 71%
httpie\manager\tasks\sessions.pyupgrade_session 100%1400 100%60 100%
httpie\manager\tasks\sessions.pycli_upgrade_session 100%100 100%00 100%
httpie\manager\tasks\sessions.pycli_upgrade_all_sessions 100%800 100%40 100%
httpie\manager\tasks\sessions.py(no function) 100%1200 100%00 100%
httpie\models.pyHTTPMessage.__init__ 100%100 100%00 100%
httpie\models.pyHTTPMessage.iter_body 0%110 100%00 0%
httpie\models.pyHTTPMessage.iter_lines 0%110 100%00 0%
httpie\models.pyHTTPMessage.headers 0%110 100%00 0%
httpie\models.pyHTTPMessage.metadata 0%110 100%00 0%
httpie\models.pyHTTPMessage.encoding 100%200 100%00 100%
httpie\models.pyHTTPMessage.content_type 100%400 100%20 100%
httpie\models.pyHTTPResponse.iter_body 100%100 100%00 100%
httpie\models.pyHTTPResponse.iter_lines 100%100 100%00 100%
httpie\models.pyHTTPResponse.headers 100%600 100%00 100%
httpie\models.pyHTTPResponse.metadata 100%600 100%00 100%
httpie\models.pyHTTPResponse.version 100%1100 100%20 100%
httpie\models.pyHTTPRequest.iter_body 100%100 100%00 100%
httpie\models.pyHTTPRequest.iter_lines 100%100 100%00 100%
httpie\models.pyHTTPRequest.headers 100%900 100%20 100%
httpie\models.pyHTTPRequest.body 100%400 100%20 100%
httpie\models.pyinfer_requests_message_kind 80%510 75%41 78%
httpie\models.pyOutputOptions.any 100%100 100%00 100%
httpie\models.pyOutputOptions.from_message 100%400 100%00 100%
httpie\models.py(no function) 100%5200 100%00 100%
httpie\output\__init__.py(no function) 100%000 100%00 100%
httpie\output\formatters\__init__.py(no function) 100%000 100%00 100%
httpie\output\formatters\colors.pyget_available_styles 100%100 100%00 100%
httpie\output\formatters\colors.pyColorFormatter.__init__ 100%1800 100%40 100%
httpie\output\formatters\colors.pyColorFormatter.format_headers 100%100 100%00 100%
httpie\output\formatters\colors.pyColorFormatter.format_body 100%400 50%21 83%
httpie\output\formatters\colors.pyColorFormatter.format_metadata 100%100 100%00 100%
httpie\output\formatters\colors.pyColorFormatter.get_lexer_for_body 100%100 100%00 100%
httpie\output\formatters\colors.pyColorFormatter.get_formatters 100%700 100%20 100%
httpie\output\formatters\colors.pyColorFormatter.get_style_class 100%400 100%00 100%
httpie\output\formatters\colors.pyget_lexer 100%3000 100%120 100%
httpie\output\formatters\colors.pymake_style 100%400 100%00 100%
httpie\output\formatters\colors.pymake_style.format_value 100%100 100%00 100%
httpie\output\formatters\colors.pymake_styles 100%400 100%20 100%
httpie\output\formatters\colors.py(no function) 100%6400 100%00 100%
httpie\output\formatters\headers.pyHeadersFormatter.__init__ 100%200 100%00 100%
httpie\output\formatters\headers.pyHeadersFormatter.format_headers 100%300 100%00 100%
httpie\output\formatters\headers.py(no function) 100%400 100%00 100%
httpie\output\formatters\json.pyJSONFormatter.__init__ 100%200 100%00 100%
httpie\output\formatters\json.pyJSONFormatter.format_body 100%900 100%20 100%
httpie\output\formatters\json.py(no function) 100%500 100%00 100%
httpie\output\formatters\xml.pyparse_xml 100%200 100%00 100%
httpie\output\formatters\xml.pyparse_declaration 100%500 75%41 89%
httpie\output\formatters\xml.pypretty_xml 100%800 75%41 92%
httpie\output\formatters\xml.pyXMLFormatter.__init__ 100%200 100%00 100%
httpie\output\formatters\xml.pyXMLFormatter.format_body 100%1300 100%20 100%
httpie\output\formatters\xml.py(no function) 100%1102 100%00 100%
httpie\output\lexers\__init__.py(no function) 100%000 100%00 100%
httpie\output\lexers\common.pyprecise 100%300 100%20 100%
httpie\output\lexers\common.py(no function) 100%100 100%00 100%
httpie\output\lexers\http.pyhttp_response_type 86%710 50%21 78%
httpie\output\lexers\http.pyrequest_method 0%220 100%00 0%
httpie\output\lexers\http.py(no function) 100%1300 100%00 100%
httpie\output\lexers\json.py(no function) 100%1000 100%00 100%
httpie\output\lexers\metadata.pyspeed_based_token 70%1030 50%42 64%
httpie\output\lexers\metadata.py(no function) 100%700 100%00 100%
httpie\output\models.pyProcessingOptions.get_prettify 100%300 100%20 100%
httpie\output\models.pyProcessingOptions.from_raw_args 100%200 100%00 100%
httpie\output\models.pyProcessingOptions.show_traceback 0%110 100%00 0%
httpie\output\models.py(no function) 100%2100 100%00 100%
httpie\output\processing.pyis_valid_mime 100%100 100%00 100%
httpie\output\processing.pyConversion.get_converter 100%400 67%62 80%
httpie\output\processing.pyFormatting.__init__ 100%700 100%60 100%
httpie\output\processing.pyFormatting.format_headers 100%300 100%20 100%
httpie\output\processing.pyFormatting.format_body 100%400 100%40 100%
httpie\output\processing.pyFormatting.format_metadata 100%300 100%20 100%
httpie\output\processing.py(no function) 100%1500 100%00 100%
httpie\output\streams.pyBaseStream.__init__ 100%500 100%00 100%
httpie\output\streams.pyBaseStream.get_headers 100%100 100%00 100%
httpie\output\streams.pyBaseStream.get_metadata 0%110 100%00 0%
httpie\output\streams.pyBaseStream.iter_body 100%000 100%00 100%
httpie\output\streams.pyBaseStream.__iter__ 100%1800 100%140 100%
httpie\output\streams.pyRawStream.__init__ 100%200 100%00 100%
httpie\output\streams.pyRawStream.iter_body 100%100 100%00 100%
httpie\output\streams.pyEncodedStream.__init__ 100%1000 100%40 100%
httpie\output\streams.pyEncodedStream.iter_body 100%500 100%40 100%
httpie\output\streams.pyEncodedStream.decode_chunk 100%300 100%00 100%
httpie\output\streams.pyEncodedStream.encoding 100%800 100%60 100%
httpie\output\streams.pyEncodedStream.encoding 0%110 100%00 0%
httpie\output\streams.pyPrettyStream.__init__ 100%300 100%00 100%
httpie\output\streams.pyPrettyStream.get_headers 100%100 100%00 100%
httpie\output\streams.pyPrettyStream.get_metadata 100%100 100%00 100%
httpie\output\streams.pyPrettyStream.iter_body 94%1810 80%102 89%
httpie\output\streams.pyPrettyStream.process_body 100%400 100%20 100%
httpie\output\streams.pyBufferedPrettyStream.iter_body 100%1100 100%80 100%
httpie\output\streams.py(no function) 100%4500 100%00 100%
httpie\output\ui\__init__.py(no function) 100%000 100%00 100%
httpie\output\ui\man_pages.pyis_available 0%770 0%20 0%
httpie\output\ui\man_pages.pydisplay_for 0%110 100%00 0%
httpie\output\ui\man_pages.py(no function) 0%880 100%00 0%
httpie\output\ui\palette.pyColorString.__or__ 50%840 50%61 50%
httpie\output\ui\palette.pyGenericColor.apply_style 50%630 50%21 50%
httpie\output\ui\palette.pyboldify 0%110 100%00 0%
httpie\output\ui\palette.pyget_color 83%610 75%41 80%
httpie\output\ui\palette.py(no function) 100%5100 100%00 100%
httpie\output\ui\rich_help.pyunpack_argument 0%990 0%40 0%
httpie\output\ui\rich_help.pyto_usage 100%2000 100%100 100%
httpie\output\ui\rich_help.pyto_help_message 0%40400 0%180 0%
httpie\output\ui\rich_help.py(no function) 100%3500 100%00 100%
httpie\output\ui\rich_palette.py_GenericColorCaster._translate 100%300 100%20 100%
httpie\output\ui\rich_palette.py_GenericColorCaster.__getitem__ 0%110 100%00 0%
httpie\output\ui\rich_palette.py_GenericColorCaster.get 100%100 100%00 100%
httpie\output\ui\rich_palette.py_make_rich_color_theme 94%1610 100%40 95%
httpie\output\ui\rich_palette.py(no function) 100%1002 100%00 100%
httpie\output\ui\rich_progress.pyBaseDisplay.start 100%001 100%00 100%
httpie\output\ui\rich_progress.pyBaseDisplay.update 100%001 100%00 100%
httpie\output\ui\rich_progress.pyBaseDisplay.stop 100%001 100%00 100%
httpie\output\ui\rich_progress.pyBaseDisplay.console 100%100 100%00 100%
httpie\output\ui\rich_progress.pyBaseDisplay._print_summary 83%1220 50%42 75%
httpie\output\ui\rich_progress.pyStatusDisplay.start 0%440 100%00 0%
httpie\output\ui\rich_progress.pyStatusDisplay.update 0%440 100%00 0%
httpie\output\ui\rich_progress.pyStatusDisplay.stop 0%440 0%20 0%
httpie\output\ui\rich_progress.pyProgressDisplay.start 100%600 100%00 100%
httpie\output\ui\rich_progress.pyProgressDisplay.update 100%100 100%00 100%
httpie\output\ui\rich_progress.pyProgressDisplay.stop 100%400 100%20 100%
httpie\output\ui\rich_progress.py(no function) 100%18010 100%00 100%
httpie\output\ui\rich_utils.pyrender_as_string 0%440 100%00 0%
httpie\output\ui\rich_utils.pyenable_highlighter 0%550 100%00 0%
httpie\output\ui\rich_utils.py(no function) 0%990 100%00 0%
httpie\output\utils.pyload_prefixed_json 100%900 100%00 100%
httpie\output\utils.pyparse_prefixed_json 100%400 100%00 100%
httpie\output\utils.py(no function) 100%700 100%00 100%
httpie\output\writer.pywrite_message 55%1150 50%61 53%
httpie\output\writer.pywrite_stream 100%800 100%40 100%
httpie\output\writer.pywrite_stream_with_colors_win 0%880 0%60 0%
httpie\output\writer.pywrite_raw_data 100%600 100%00 100%
httpie\output\writer.pybuild_output_stream_for_message 100%700 100%40 100%
httpie\output\writer.pyget_stream_type_and_kwargs 100%1800 100%100 100%
httpie\output\writer.py(no function) 100%1800 100%00 100%
httpie\plugins\__init__.py(no function) 100%200 100%00 100%
httpie\plugins\base.pyAuthPlugin.get_auth 0%110 100%00 0%
httpie\plugins\base.pyTransportPlugin.get_adapter 0%110 100%00 0%
httpie\plugins\base.pyConverterPlugin.__init__ 100%100 100%00 100%
httpie\plugins\base.pyConverterPlugin.convert 0%110 100%00 0%
httpie\plugins\base.pyConverterPlugin.supports 0%110 100%00 0%
httpie\plugins\base.pyFormatterPlugin.__init__ 100%300 100%00 100%
httpie\plugins\base.pyFormatterPlugin.format_headers 100%100 100%00 100%
httpie\plugins\base.pyFormatterPlugin.format_body 100%100 100%00 100%
httpie\plugins\base.pyFormatterPlugin.format_metadata 100%100 100%00 100%
httpie\plugins\base.py(no function) 100%2700 100%00 100%
httpie\plugins\builtin.pyHTTPBasicAuth.__call__ 100%200 100%00 100%
httpie\plugins\builtin.pyHTTPBasicAuth.make_header 100%300 100%00 100%
httpie\plugins\builtin.pyHTTPBearerAuth.__init__ 100%100 100%00 100%
httpie\plugins\builtin.pyHTTPBearerAuth.__call__ 100%200 100%00 100%
httpie\plugins\builtin.pyBasicAuthPlugin.get_auth 100%100 100%00 100%
httpie\plugins\builtin.pyDigestAuthPlugin.get_auth 100%100 100%00 100%
httpie\plugins\builtin.pyBearerAuthPlugin.get_auth 100%100 100%00 100%
httpie\plugins\builtin.py(no function) 100%2800 100%00 100%
httpie\plugins\manager.py_load_directories 100%600 100%20 100%
httpie\plugins\manager.pyenable_plugins 67%310 50%21 60%
httpie\plugins\manager.pyPluginManager.register 100%200 100%20 100%
httpie\plugins\manager.pyPluginManager.unregister 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.filter 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.iter_entry_points 100%400 100%20 100%
httpie\plugins\manager.pyPluginManager.load_installed_plugins 100%900 100%20 100%
httpie\plugins\manager.pyPluginManager.get_auth_plugins 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_auth_plugin_mapping 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_auth_plugin 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_formatters 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_formatters_grouped 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_converters 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.get_transport_plugins 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.__str__ 100%100 100%00 100%
httpie\plugins\manager.pyPluginManager.__repr__ 100%100 100%00 100%
httpie\plugins\manager.py(no function) 100%3200 100%00 100%
httpie\plugins\registry.py(no function) 100%800 100%00 100%
httpie\sessions.pyis_anonymous_session 100%100 100%00 100%
httpie\sessions.pysession_hostname_to_dirname 100%200 100%00 100%
httpie\sessions.pystrip_port 100%100 100%00 100%
httpie\sessions.pymaterialize_cookie 100%400 100%20 100%
httpie\sessions.pymaterialize_cookies 100%100 100%00 100%
httpie\sessions.pymaterialize_headers 100%100 100%00 100%
httpie\sessions.pyget_httpie_session 91%1110 75%41 87%
httpie\sessions.pySession.__init__ 100%1000 100%00 100%
httpie\sessions.pySession._add_cookies 100%600 100%40 100%
httpie\sessions.pySession.pre_process_data 100%700 100%40 100%
httpie\sessions.pySession.post_process_data 100%500 100%20 100%
httpie\sessions.pySession._compute_new_headers 100%2100 94%181 97%
httpie\sessions.pySession.update_headers 100%700 100%40 100%
httpie\sessions.pySession.headers 100%100 100%00 100%
httpie\sessions.pySession.cookies 100%200 100%00 100%
httpie\sessions.pySession.cookies 100%100 100%00 100%
httpie\sessions.pySession.remove_cookies 100%200 100%20 100%
httpie\sessions.pySession.auth 86%1420 100%40 89%
httpie\sessions.pySession.auth 100%200 100%00 100%
httpie\sessions.pySession.is_anonymous 100%100 100%00 100%
httpie\sessions.pySession.warn_legacy_usage 100%400 100%20 100%
httpie\sessions.py(no function) 100%5100 100%00 100%
httpie\ssl_.pyHTTPieCertificate.to_raw_cert 100%100 100%00 100%
httpie\ssl_.pyHTTPieHTTPSAdapter.__init__ 100%200 100%00 100%
httpie\ssl_.pyHTTPieHTTPSAdapter.init_poolmanager 100%200 100%00 100%
httpie\ssl_.pyHTTPieHTTPSAdapter.proxy_manager_for 0%220 100%00 0%
httpie\ssl_.pyHTTPieHTTPSAdapter.cert_verify 100%400 100%20 100%
httpie\ssl_.pyHTTPieHTTPSAdapter._create_ssl_context 100%300 100%00 100%
httpie\ssl_.pyHTTPieHTTPSAdapter.get_default_ciphers_names 100%100 100%00 100%
httpie\ssl_.py_is_key_file_encrypted 100%500 100%40 100%
httpie\ssl_.py(no function) 100%2300 100%00 100%
httpie\status.pyhttp_status_to_exit_status 100%700 100%60 100%
httpie\status.py(no function) 100%1300 100%00 100%
httpie\uploads.pyChunkedStream.__iter__ 0%110 100%00 0%
httpie\uploads.pyChunkedUploadStream.__init__ 100%300 100%00 100%
httpie\uploads.pyChunkedUploadStream.__iter__ 100%500 100%40 100%
httpie\uploads.pyChunkedMultipartUploadStream.__init__ 100%200 100%00 100%
httpie\uploads.pyChunkedMultipartUploadStream.__iter__ 100%700 75%41 91%
httpie\uploads.pyas_bytes 100%300 100%20 100%
httpie\uploads.py_wrap_function_with_callback 100%200 100%00 100%
httpie\uploads.py_wrap_function_with_callback.wrapped 100%300 100%00 100%
httpie\uploads.pyis_stdin 80%510 100%00 80%
httpie\uploads.pyobserve_stdin_for_data_thread 0%770 0%40 0%
httpie\uploads.pyobserve_stdin_for_data_thread.worker 0%220 0%20 0%
httpie\uploads.py_read_file_with_selectors 29%750 25%41 27%
httpie\uploads.py_prepare_file_for_upload 92%1310 90%101 91%
httpie\uploads.pyprepare_request_body 100%1500 100%120 100%
httpie\uploads.pyget_multipart_data_and_content_type 100%900 100%40 100%
httpie\uploads.pycompress_request 92%1310 83%61 89%
httpie\uploads.py(no function) 100%3302 100%00 100%
httpie\utils.pyJsonDictPreservingDuplicateKeys.__init__ 100%200 100%00 100%
httpie\utils.pyJsonDictPreservingDuplicateKeys._ensure_items_used 100%200 100%20 100%
httpie\utils.pyJsonDictPreservingDuplicateKeys.items 100%100 100%00 100%
httpie\utils.pyload_json_preserve_order_and_dupe_keys 100%100 100%00 100%
httpie\utils.pyrepr_dict 100%100 100%00 100%
httpie\utils.pyhumanize_bytes 0%770 0%60 0%
httpie\utils.pyExplicitNullAuth.__call__ 100%100 100%00 100%
httpie\utils.pyget_content_type 100%100 100%00 100%
httpie\utils.pysplit_cookies 100%300 100%20 100%
httpie\utils.pyget_expired_cookies 100%600 100%00 100%
httpie\utils.pyget_expired_cookies.is_expired 100%100 100%00 100%
httpie\utils.py_max_age_to_expires 100%600 100%60 100%
httpie\utils.pyparse_content_type_header 100%1400 67%62 90%
httpie\utils.pyas_site 100%200 100%00 100%
httpie\utils.pyget_site_paths 43%740 25%41 36%
httpie\utils.pysplit_iterable 100%600 100%40 100%
httpie\utils.pyunwrap_context 100%400 100%20 100%
httpie\utils.pyurl_as_host 100%100 100%00 100%
httpie\utils.pyopen_with_lockfile 80%1020 100%00 80%
httpie\utils.pyis_version_greater 100%200 100%00 100%
httpie\utils.pyis_version_greater.split_version 71%720 100%20 78%
httpie\utils.py(no function) 100%4600 100%00 100%
Total  91%418739030 81%1126111 89%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/index.html b/courseProjectDocs/Setup/testCoverage/index.html new file mode 100644 index 0000000000..e54a5848db --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/index.html @@ -0,0 +1,1221 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 89% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.16.2, + created at 2026-09-28 11:59 -0400 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  Statements Branches Total
File coveragestatementsmissingexcluded coveragebranchespartial coverage
httpie\__init__.py 100%400 100%00 100%
httpie\__main__.py 100%803 100%00 100%
httpie\adapters.py 100%700 100%00 100%
httpie\cli\__init__.py 100%000 100%00 100%
httpie\cli\argparser.py 94%316200 90%14611 92%
httpie\cli\argtypes.py 95%13670 97%321 95%
httpie\cli\constants.py 100%4600 100%00 100%
httpie\cli\definition.py 93%10880 50%63 90%
httpie\cli\dicts.py 98%4010 88%162 95%
httpie\cli\exceptions.py 100%200 100%00 100%
httpie\cli\nested_json\__init__.py 100%400 100%00 100%
httpie\cli\nested_json\errors.py 100%1400 100%20 100%
httpie\cli\nested_json\interpret.py 96%7730 89%384 94%
httpie\cli\nested_json\parse.py 95%10750 90%424 94%
httpie\cli\nested_json\tokens.py 92%4940 58%121 85%
httpie\cli\options.py 98%12430 97%301 97%
httpie\cli\requestitems.py 92%10080 100%160 93%
httpie\cli\utils.py 95%4120 100%60 96%
httpie\client.py 100%17200 96%683 99%
httpie\compat.py 45%56310 28%185 41%
httpie\config.py 92%9580 79%141 90%
httpie\context.py 92%12092 68%225 89%
httpie\cookies.py 100%1100 100%20 100%
httpie\core.py 95%17080 85%629 93%
httpie\downloads.py 96%17570 83%529 93%
httpie\encoding.py 100%1800 83%61 96%
httpie\internal\__build_channel__.py 100%100 100%00 100%
httpie\internal\__init__.py 100%000 100%00 100%
httpie\internal\daemon_runner.py 86%2840 100%00 86%
httpie\internal\daemons.py 57%58250 25%123 51%
httpie\internal\update_warnings.py 95%9650 75%244 91%
httpie\legacy\__init__.py 100%000 100%00 100%
httpie\legacy\v3_1_0_session_cookie_format.py 100%3102 94%161 98%
httpie\legacy\v3_2_0_session_header_format.py 100%2502 100%100 100%
httpie\manager\__init__.py 100%000 100%00 100%
httpie\manager\__main__.py 85%3352 100%40 86%
httpie\manager\cli.py 100%3900 100%80 100%
httpie\manager\compat.py 63%35130 12%81 53%
httpie\manager\core.py 95%2010 88%81 93%
httpie\manager\tasks\__init__.py 100%500 100%00 100%
httpie\manager\tasks\check_updates.py 100%800 100%00 100%
httpie\manager\tasks\export_args.py 93%1410 50%21 88%
httpie\manager\tasks\plugins.py 80%153310 52%6011 72%
httpie\manager\tasks\sessions.py 95%4320 88%162 93%
httpie\models.py 96%11350 92%121 95%
httpie\output\__init__.py 100%000 100%00 100%
httpie\output\formatters\__init__.py 100%000 100%00 100%
httpie\output\formatters\colors.py 100%14000 95%221 99%
httpie\output\formatters\headers.py 100%900 100%00 100%
httpie\output\formatters\json.py 100%1600 100%20 100%
httpie\output\formatters\xml.py 100%4102 80%102 96%
httpie\output\lexers\__init__.py 100%000 100%00 100%
httpie\output\lexers\common.py 100%400 100%20 100%
httpie\output\lexers\http.py 86%2230 50%21 83%
httpie\output\lexers\json.py 100%1000 100%00 100%
httpie\output\lexers\metadata.py 82%1730 50%42 76%
httpie\output\models.py 96%2710 100%20 97%
httpie\output\processing.py 100%3700 90%202 96%
httpie\output\streams.py 98%13830 96%482 97%
httpie\output\ui\__init__.py 100%000 100%00 100%
httpie\output\ui\man_pages.py 0%16160 0%20 0%
httpie\output\ui\palette.py 88%7290 58%123 83%
httpie\output\ui\rich_help.py 53%104490 31%320 48%
httpie\output\ui\rich_palette.py 94%3122 100%60 95%
httpie\output\ui\rich_progress.py 74%541413 50%82 71%
httpie\output\ui\rich_utils.py 0%18180 100%00 0%
httpie\output\utils.py 100%2000 100%00 100%
httpie\output\writer.py 83%76130 70%301 79%
httpie\plugins\__init__.py 100%200 100%00 100%
httpie\plugins\base.py 89%3840 100%00 89%
httpie\plugins\builtin.py 100%3900 100%00 100%
httpie\plugins\manager.py 99%6710 90%101 97%
httpie\plugins\registry.py 100%800 100%00 100%
httpie\sessions.py 98%15530 96%462 98%
httpie\ssl_.py 95%4320 100%60 96%
httpie\status.py 100%2000 100%60 100%
httpie\uploads.py 86%130182 77%524 84%
httpie\utils.py 89%131150 68%343 84%
Total 91%418739030 81%1126111 89%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/keybd_closed_cb_900cfef5.png b/courseProjectDocs/Setup/testCoverage/keybd_closed_cb_900cfef5.png new file mode 100644 index 0000000000..ba119c47df Binary files /dev/null and b/courseProjectDocs/Setup/testCoverage/keybd_closed_cb_900cfef5.png differ diff --git a/courseProjectDocs/Setup/testCoverage/status.json b/courseProjectDocs/Setup/testCoverage/status.json new file mode 100644 index 0000000000..0b4aba4f96 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/status.json @@ -0,0 +1 @@ +{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.16.2","globals":"93a6ac3101dc77ab907c7c7ad83fbed7","files":{"z_4802d84346c9dc29___init___py":{"hash":"1e3ea6e412eeeab1d1ac3732d3398201","index":{"url":"z_4802d84346c9dc29___init___py.html","file":"httpie\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":4,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29___main___py":{"hash":"adc6d6792cdb824271b0c991e3c136d4","index":{"url":"z_4802d84346c9dc29___main___py.html","file":"httpie\\__main__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":8,"n_excluded":3,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_adapters_py":{"hash":"67169ce208dba2c71d556bce304cb7a5","index":{"url":"z_4802d84346c9dc29_adapters_py.html","file":"httpie\\adapters.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":7,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_6ee826626d1566e3___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_6ee826626d1566e3___init___py.html","file":"httpie\\cli\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_6ee826626d1566e3_argparser_py":{"hash":"e97f9b76b519ad344c8a26e634e59438","index":{"url":"z_6ee826626d1566e3_argparser_py.html","file":"httpie\\cli\\argparser.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":316,"n_excluded":0,"n_missing":20,"n_branches":146,"n_partial_branches":11,"n_missing_branches":15}}},"z_6ee826626d1566e3_argtypes_py":{"hash":"4a8adca433c3b6efd8a6fa4d6a8c8be0","index":{"url":"z_6ee826626d1566e3_argtypes_py.html","file":"httpie\\cli\\argtypes.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":136,"n_excluded":0,"n_missing":7,"n_branches":32,"n_partial_branches":1,"n_missing_branches":1}}},"z_6ee826626d1566e3_constants_py":{"hash":"ddff4246d31de2ea6abc9cf045388a53","index":{"url":"z_6ee826626d1566e3_constants_py.html","file":"httpie\\cli\\constants.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":46,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_6ee826626d1566e3_definition_py":{"hash":"bd02d62bc8e34d1ed4990e51cf01f9f8","index":{"url":"z_6ee826626d1566e3_definition_py.html","file":"httpie\\cli\\definition.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":108,"n_excluded":0,"n_missing":8,"n_branches":6,"n_partial_branches":3,"n_missing_branches":3}}},"z_6ee826626d1566e3_dicts_py":{"hash":"abbaa478f42bbcb89ff84886e67acf4a","index":{"url":"z_6ee826626d1566e3_dicts_py.html","file":"httpie\\cli\\dicts.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":40,"n_excluded":0,"n_missing":1,"n_branches":16,"n_partial_branches":2,"n_missing_branches":2}}},"z_6ee826626d1566e3_exceptions_py":{"hash":"f25610083c6518b6c7eeca3fe654ba3d","index":{"url":"z_6ee826626d1566e3_exceptions_py.html","file":"httpie\\cli\\exceptions.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":2,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_ed165166c68eaac4___init___py":{"hash":"982fc4be79e803a997c11c8e639d79c1","index":{"url":"z_ed165166c68eaac4___init___py.html","file":"httpie\\cli\\nested_json\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":4,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_ed165166c68eaac4_errors_py":{"hash":"35f7eabe1cc6a23854f7275ba3c92ea2","index":{"url":"z_ed165166c68eaac4_errors_py.html","file":"httpie\\cli\\nested_json\\errors.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":14,"n_excluded":0,"n_missing":0,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_ed165166c68eaac4_interpret_py":{"hash":"3c4915a92b09681d6aec077a3fccdba5","index":{"url":"z_ed165166c68eaac4_interpret_py.html","file":"httpie\\cli\\nested_json\\interpret.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":77,"n_excluded":0,"n_missing":3,"n_branches":38,"n_partial_branches":4,"n_missing_branches":4}}},"z_ed165166c68eaac4_parse_py":{"hash":"3b5e3ebf7aca44944b42265250ae591e","index":{"url":"z_ed165166c68eaac4_parse_py.html","file":"httpie\\cli\\nested_json\\parse.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":107,"n_excluded":0,"n_missing":5,"n_branches":42,"n_partial_branches":4,"n_missing_branches":4}}},"z_ed165166c68eaac4_tokens_py":{"hash":"9b7637b8f2a65532ac21b94aef2f1d3d","index":{"url":"z_ed165166c68eaac4_tokens_py.html","file":"httpie\\cli\\nested_json\\tokens.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":49,"n_excluded":0,"n_missing":4,"n_branches":12,"n_partial_branches":1,"n_missing_branches":5}}},"z_6ee826626d1566e3_options_py":{"hash":"09fd487ec19d750c2065107297a4c095","index":{"url":"z_6ee826626d1566e3_options_py.html","file":"httpie\\cli\\options.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":124,"n_excluded":0,"n_missing":3,"n_branches":30,"n_partial_branches":1,"n_missing_branches":1}}},"z_6ee826626d1566e3_requestitems_py":{"hash":"c5b99d156893cca764a56f1b411c0d92","index":{"url":"z_6ee826626d1566e3_requestitems_py.html","file":"httpie\\cli\\requestitems.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":100,"n_excluded":0,"n_missing":8,"n_branches":16,"n_partial_branches":0,"n_missing_branches":0}}},"z_6ee826626d1566e3_utils_py":{"hash":"f89f0f752d076bbdd80b23d2f624b16f","index":{"url":"z_6ee826626d1566e3_utils_py.html","file":"httpie\\cli\\utils.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":41,"n_excluded":0,"n_missing":2,"n_branches":6,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_client_py":{"hash":"5c9cee02f8229847a02d5a706eee4a78","index":{"url":"z_4802d84346c9dc29_client_py.html","file":"httpie\\client.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":172,"n_excluded":0,"n_missing":0,"n_branches":68,"n_partial_branches":3,"n_missing_branches":3}}},"z_4802d84346c9dc29_compat_py":{"hash":"59708dd5979ae827b41382a2157cec82","index":{"url":"z_4802d84346c9dc29_compat_py.html","file":"httpie\\compat.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":56,"n_excluded":0,"n_missing":31,"n_branches":18,"n_partial_branches":5,"n_missing_branches":13}}},"z_4802d84346c9dc29_config_py":{"hash":"b4c11cec3752b917dba26289c4ec8b34","index":{"url":"z_4802d84346c9dc29_config_py.html","file":"httpie\\config.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":95,"n_excluded":0,"n_missing":8,"n_branches":14,"n_partial_branches":1,"n_missing_branches":3}}},"z_4802d84346c9dc29_context_py":{"hash":"b4e03274c97b8608aae3c8da10278629","index":{"url":"z_4802d84346c9dc29_context_py.html","file":"httpie\\context.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":120,"n_excluded":2,"n_missing":9,"n_branches":22,"n_partial_branches":5,"n_missing_branches":7}}},"z_4802d84346c9dc29_cookies_py":{"hash":"844aa033d39766967de8536b70ad54ac","index":{"url":"z_4802d84346c9dc29_cookies_py.html","file":"httpie\\cookies.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":11,"n_excluded":0,"n_missing":0,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_core_py":{"hash":"75c4980f6b27bf6ad72a53413d5d540b","index":{"url":"z_4802d84346c9dc29_core_py.html","file":"httpie\\core.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":170,"n_excluded":0,"n_missing":8,"n_branches":62,"n_partial_branches":9,"n_missing_branches":9}}},"z_4802d84346c9dc29_downloads_py":{"hash":"e6aaed3d1a1ca1248b47b0fe13773685","index":{"url":"z_4802d84346c9dc29_downloads_py.html","file":"httpie\\downloads.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":175,"n_excluded":0,"n_missing":7,"n_branches":52,"n_partial_branches":9,"n_missing_branches":9}}},"z_4802d84346c9dc29_encoding_py":{"hash":"b0cb4e2dcd7e5d30c34d0d2c926c5772","index":{"url":"z_4802d84346c9dc29_encoding_py.html","file":"httpie\\encoding.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":18,"n_excluded":0,"n_missing":0,"n_branches":6,"n_partial_branches":1,"n_missing_branches":1}}},"z_c2aeaf7156838c3b___build_channel___py":{"hash":"a09a448a81b0c2a2a6067d94ef52cc56","index":{"url":"z_c2aeaf7156838c3b___build_channel___py.html","file":"httpie\\internal\\__build_channel__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":1,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c2aeaf7156838c3b___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_c2aeaf7156838c3b___init___py.html","file":"httpie\\internal\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c2aeaf7156838c3b_daemon_runner_py":{"hash":"deb7527873cd01092c81e11120988bba","index":{"url":"z_c2aeaf7156838c3b_daemon_runner_py.html","file":"httpie\\internal\\daemon_runner.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":28,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c2aeaf7156838c3b_daemons_py":{"hash":"f69092a4c871c0b2d9fc788f1456ab11","index":{"url":"z_c2aeaf7156838c3b_daemons_py.html","file":"httpie\\internal\\daemons.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":58,"n_excluded":0,"n_missing":25,"n_branches":12,"n_partial_branches":3,"n_missing_branches":9}}},"z_c2aeaf7156838c3b_update_warnings_py":{"hash":"396d74c5b388396d27f8ef9fb2747cde","index":{"url":"z_c2aeaf7156838c3b_update_warnings_py.html","file":"httpie\\internal\\update_warnings.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":96,"n_excluded":0,"n_missing":5,"n_branches":24,"n_partial_branches":4,"n_missing_branches":6}}},"z_d97044aac8d9c56e___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_d97044aac8d9c56e___init___py.html","file":"httpie\\legacy\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_d97044aac8d9c56e_v3_1_0_session_cookie_format_py":{"hash":"975918495d0ef30eea76237ef1f4be97","index":{"url":"z_d97044aac8d9c56e_v3_1_0_session_cookie_format_py.html","file":"httpie\\legacy\\v3_1_0_session_cookie_format.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":31,"n_excluded":2,"n_missing":0,"n_branches":16,"n_partial_branches":1,"n_missing_branches":1}}},"z_d97044aac8d9c56e_v3_2_0_session_header_format_py":{"hash":"2f22aa60b82d1b89a445815890e0cf7e","index":{"url":"z_d97044aac8d9c56e_v3_2_0_session_header_format_py.html","file":"httpie\\legacy\\v3_2_0_session_header_format.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":25,"n_excluded":2,"n_missing":0,"n_branches":10,"n_partial_branches":0,"n_missing_branches":0}}},"z_7355ca5fa883be39___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_7355ca5fa883be39___init___py.html","file":"httpie\\manager\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7355ca5fa883be39___main___py":{"hash":"36fc1c5a4cd7a0eb7a92e5e8cfcee45d","index":{"url":"z_7355ca5fa883be39___main___py.html","file":"httpie\\manager\\__main__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":2,"n_missing":5,"n_branches":4,"n_partial_branches":0,"n_missing_branches":0}}},"z_7355ca5fa883be39_cli_py":{"hash":"aba2b943d9f01465bccc41f4203b63ef","index":{"url":"z_7355ca5fa883be39_cli_py.html","file":"httpie\\manager\\cli.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":39,"n_excluded":0,"n_missing":0,"n_branches":8,"n_partial_branches":0,"n_missing_branches":0}}},"z_7355ca5fa883be39_compat_py":{"hash":"8d214c9f6d08649ce2c934efb6de2f0a","index":{"url":"z_7355ca5fa883be39_compat_py.html","file":"httpie\\manager\\compat.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":35,"n_excluded":0,"n_missing":13,"n_branches":8,"n_partial_branches":1,"n_missing_branches":7}}},"z_7355ca5fa883be39_core_py":{"hash":"d219318afa7a4642f40455970da149ca","index":{"url":"z_7355ca5fa883be39_core_py.html","file":"httpie\\manager\\core.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":20,"n_excluded":0,"n_missing":1,"n_branches":8,"n_partial_branches":1,"n_missing_branches":1}}},"z_7d042ac8ba564a7a___init___py":{"hash":"14310b0455ee0381db13e705ceaff2e1","index":{"url":"z_7d042ac8ba564a7a___init___py.html","file":"httpie\\manager\\tasks\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":5,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7d042ac8ba564a7a_check_updates_py":{"hash":"ac663c50816e9374da68335ce60c3901","index":{"url":"z_7d042ac8ba564a7a_check_updates_py.html","file":"httpie\\manager\\tasks\\check_updates.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":8,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_7d042ac8ba564a7a_export_args_py":{"hash":"5cc573bb6587c8cf06820085504acfe6","index":{"url":"z_7d042ac8ba564a7a_export_args_py.html","file":"httpie\\manager\\tasks\\export_args.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":14,"n_excluded":0,"n_missing":1,"n_branches":2,"n_partial_branches":1,"n_missing_branches":1}}},"z_7d042ac8ba564a7a_plugins_py":{"hash":"9ebb3aca3f180748cb12c0fdff7eac27","index":{"url":"z_7d042ac8ba564a7a_plugins_py.html","file":"httpie\\manager\\tasks\\plugins.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":153,"n_excluded":0,"n_missing":31,"n_branches":60,"n_partial_branches":11,"n_missing_branches":29}}},"z_7d042ac8ba564a7a_sessions_py":{"hash":"9bbb9a799479cbcdea54cd724739e9fb","index":{"url":"z_7d042ac8ba564a7a_sessions_py.html","file":"httpie\\manager\\tasks\\sessions.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":43,"n_excluded":0,"n_missing":2,"n_branches":16,"n_partial_branches":2,"n_missing_branches":2}}},"z_4802d84346c9dc29_models_py":{"hash":"699f1a4dc831a126909b6d83b2a111a6","index":{"url":"z_4802d84346c9dc29_models_py.html","file":"httpie\\models.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":113,"n_excluded":0,"n_missing":5,"n_branches":12,"n_partial_branches":1,"n_missing_branches":1}}},"z_63fc1e7d517295a4___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_63fc1e7d517295a4___init___py.html","file":"httpie\\output\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_35ca103ad7845a7a___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_35ca103ad7845a7a___init___py.html","file":"httpie\\output\\formatters\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_35ca103ad7845a7a_colors_py":{"hash":"65c6d90ac0878c7e57ea961d75cf6186","index":{"url":"z_35ca103ad7845a7a_colors_py.html","file":"httpie\\output\\formatters\\colors.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":140,"n_excluded":0,"n_missing":0,"n_branches":22,"n_partial_branches":1,"n_missing_branches":1}}},"z_35ca103ad7845a7a_headers_py":{"hash":"c7798e051843c9b7ebb5fd2abf817f64","index":{"url":"z_35ca103ad7845a7a_headers_py.html","file":"httpie\\output\\formatters\\headers.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":9,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_35ca103ad7845a7a_json_py":{"hash":"b616a0c686da073cd03d5f415041db78","index":{"url":"z_35ca103ad7845a7a_json_py.html","file":"httpie\\output\\formatters\\json.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":16,"n_excluded":0,"n_missing":0,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_35ca103ad7845a7a_xml_py":{"hash":"f69350dd064ea3ac42974fb76bab2c36","index":{"url":"z_35ca103ad7845a7a_xml_py.html","file":"httpie\\output\\formatters\\xml.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":41,"n_excluded":2,"n_missing":0,"n_branches":10,"n_partial_branches":2,"n_missing_branches":2}}},"z_a58f12c5cdcdd191___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_a58f12c5cdcdd191___init___py.html","file":"httpie\\output\\lexers\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_a58f12c5cdcdd191_common_py":{"hash":"cc8ec893931367cd5756cc1e63087cfd","index":{"url":"z_a58f12c5cdcdd191_common_py.html","file":"httpie\\output\\lexers\\common.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":4,"n_excluded":0,"n_missing":0,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_a58f12c5cdcdd191_http_py":{"hash":"1516e6e6932c4792e9f0545d88c6aef2","index":{"url":"z_a58f12c5cdcdd191_http_py.html","file":"httpie\\output\\lexers\\http.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":22,"n_excluded":0,"n_missing":3,"n_branches":2,"n_partial_branches":1,"n_missing_branches":1}}},"z_a58f12c5cdcdd191_json_py":{"hash":"4ca0584fb71e4e7e79f028c2f8750cea","index":{"url":"z_a58f12c5cdcdd191_json_py.html","file":"httpie\\output\\lexers\\json.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":10,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_a58f12c5cdcdd191_metadata_py":{"hash":"d6338dbb0e42d242d6533d5716082f38","index":{"url":"z_a58f12c5cdcdd191_metadata_py.html","file":"httpie\\output\\lexers\\metadata.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":17,"n_excluded":0,"n_missing":3,"n_branches":4,"n_partial_branches":2,"n_missing_branches":2}}},"z_63fc1e7d517295a4_models_py":{"hash":"70de048f3aa5e74e25755e7c2543280c","index":{"url":"z_63fc1e7d517295a4_models_py.html","file":"httpie\\output\\models.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":27,"n_excluded":0,"n_missing":1,"n_branches":2,"n_partial_branches":0,"n_missing_branches":0}}},"z_63fc1e7d517295a4_processing_py":{"hash":"2c07613d15a75cf8fd0df14a8cf7dba2","index":{"url":"z_63fc1e7d517295a4_processing_py.html","file":"httpie\\output\\processing.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":37,"n_excluded":0,"n_missing":0,"n_branches":20,"n_partial_branches":2,"n_missing_branches":2}}},"z_63fc1e7d517295a4_streams_py":{"hash":"56c4080eaf2510f7956c5f2619affbee","index":{"url":"z_63fc1e7d517295a4_streams_py.html","file":"httpie\\output\\streams.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":138,"n_excluded":0,"n_missing":3,"n_branches":48,"n_partial_branches":2,"n_missing_branches":2}}},"z_25e3941e266507d4___init___py":{"hash":"fc5d0cbc927c92f363d6fb911128a9b7","index":{"url":"z_25e3941e266507d4___init___py.html","file":"httpie\\output\\ui\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_25e3941e266507d4_man_pages_py":{"hash":"3fb27c62e21dd05accc401dbf47e2d75","index":{"url":"z_25e3941e266507d4_man_pages_py.html","file":"httpie\\output\\ui\\man_pages.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":16,"n_excluded":0,"n_missing":16,"n_branches":2,"n_partial_branches":0,"n_missing_branches":2}}},"z_25e3941e266507d4_palette_py":{"hash":"d6bf4e1650bf69e34e31c81304025943","index":{"url":"z_25e3941e266507d4_palette_py.html","file":"httpie\\output\\ui\\palette.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":72,"n_excluded":0,"n_missing":9,"n_branches":12,"n_partial_branches":3,"n_missing_branches":5}}},"z_25e3941e266507d4_rich_help_py":{"hash":"fcdd2292e78b9f5dd9635f2874607119","index":{"url":"z_25e3941e266507d4_rich_help_py.html","file":"httpie\\output\\ui\\rich_help.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":104,"n_excluded":0,"n_missing":49,"n_branches":32,"n_partial_branches":0,"n_missing_branches":22}}},"z_25e3941e266507d4_rich_palette_py":{"hash":"ef0052d962a81e561ae6560d199ddc9f","index":{"url":"z_25e3941e266507d4_rich_palette_py.html","file":"httpie\\output\\ui\\rich_palette.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":31,"n_excluded":2,"n_missing":2,"n_branches":6,"n_partial_branches":0,"n_missing_branches":0}}},"z_25e3941e266507d4_rich_progress_py":{"hash":"de33599a8d117ec6395236a134db7e15","index":{"url":"z_25e3941e266507d4_rich_progress_py.html","file":"httpie\\output\\ui\\rich_progress.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":54,"n_excluded":13,"n_missing":14,"n_branches":8,"n_partial_branches":2,"n_missing_branches":4}}},"z_25e3941e266507d4_rich_utils_py":{"hash":"2875f810c0ef35f35b1615e6851ea1b8","index":{"url":"z_25e3941e266507d4_rich_utils_py.html","file":"httpie\\output\\ui\\rich_utils.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":18,"n_excluded":0,"n_missing":18,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_63fc1e7d517295a4_utils_py":{"hash":"c02b8ff7ed14b59a53e7e66836595b04","index":{"url":"z_63fc1e7d517295a4_utils_py.html","file":"httpie\\output\\utils.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":20,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_63fc1e7d517295a4_writer_py":{"hash":"715b72039cb142bfa6a86ff5139d63db","index":{"url":"z_63fc1e7d517295a4_writer_py.html","file":"httpie\\output\\writer.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":76,"n_excluded":0,"n_missing":13,"n_branches":30,"n_partial_branches":1,"n_missing_branches":9}}},"z_cd3f0923f62395a4___init___py":{"hash":"8cc335d04a6727d30578248355dbc35a","index":{"url":"z_cd3f0923f62395a4___init___py.html","file":"httpie\\plugins\\__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":2,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_cd3f0923f62395a4_base_py":{"hash":"7250111ad8114d2c90ebf11b6d2cf11d","index":{"url":"z_cd3f0923f62395a4_base_py.html","file":"httpie\\plugins\\base.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":38,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_cd3f0923f62395a4_builtin_py":{"hash":"a7aec7c6062e32b1f73b2f1c04e12ce6","index":{"url":"z_cd3f0923f62395a4_builtin_py.html","file":"httpie\\plugins\\builtin.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":39,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_cd3f0923f62395a4_manager_py":{"hash":"299f55a1e1d8b514ff9959dc40c1a65c","index":{"url":"z_cd3f0923f62395a4_manager_py.html","file":"httpie\\plugins\\manager.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":67,"n_excluded":0,"n_missing":1,"n_branches":10,"n_partial_branches":1,"n_missing_branches":1}}},"z_cd3f0923f62395a4_registry_py":{"hash":"ea6f1f9374a7c2e9a7bdc62cf2c8ec82","index":{"url":"z_cd3f0923f62395a4_registry_py.html","file":"httpie\\plugins\\registry.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":8,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_sessions_py":{"hash":"42dc9c4dc977165102dff6f905489052","index":{"url":"z_4802d84346c9dc29_sessions_py.html","file":"httpie\\sessions.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":155,"n_excluded":0,"n_missing":3,"n_branches":46,"n_partial_branches":2,"n_missing_branches":2}}},"z_4802d84346c9dc29_ssl__py":{"hash":"7bfe582c067a89421024ec29f0082e3f","index":{"url":"z_4802d84346c9dc29_ssl__py.html","file":"httpie\\ssl_.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":43,"n_excluded":0,"n_missing":2,"n_branches":6,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_status_py":{"hash":"dee884b2374a966507b11a3373af8ecb","index":{"url":"z_4802d84346c9dc29_status_py.html","file":"httpie\\status.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":20,"n_excluded":0,"n_missing":0,"n_branches":6,"n_partial_branches":0,"n_missing_branches":0}}},"z_4802d84346c9dc29_uploads_py":{"hash":"0b611ea826acd1421af981b70371a348","index":{"url":"z_4802d84346c9dc29_uploads_py.html","file":"httpie\\uploads.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":130,"n_excluded":2,"n_missing":18,"n_branches":52,"n_partial_branches":4,"n_missing_branches":12}}},"z_4802d84346c9dc29_utils_py":{"hash":"0a27fb8fd535ae5332202575f7af79ae","index":{"url":"z_4802d84346c9dc29_utils_py.html","file":"httpie\\utils.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":131,"n_excluded":0,"n_missing":15,"n_branches":34,"n_partial_branches":3,"n_missing_branches":11}}}}} \ No newline at end of file diff --git a/courseProjectDocs/Setup/testCoverage/style_cb_0853b3de.css b/courseProjectDocs/Setup/testCoverage/style_cb_0853b3de.css new file mode 100644 index 0000000000..f82a185d8e --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/style_cb_0853b3de.css @@ -0,0 +1,391 @@ +@charset "UTF-8"; +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { body { color: #eee; } } + +html > body { font-size: 16px; } + +a:active, a:focus { outline: 2px dashed #007acc; } + +p { font-size: .875em; line-height: 1.4em; } + +table { border-collapse: collapse; } + +td { vertical-align: top; } + +table tr.hidden { display: none !important; } + +p#no_rows { display: none; font-size: 1.15em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +a.nav { text-decoration: none; color: inherit; } + +a.nav:hover { text-decoration: underline; color: inherit; } + +.hidden { display: none; } + +header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } + +@media (prefers-color-scheme: dark) { header { background: black; } } + +@media (prefers-color-scheme: dark) { header { border-color: #333; } } + +header .content { padding: 1rem 3.5rem; } + +header h2 { margin-top: .5em; font-size: 1em; } + +header h2 a.button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header h2 a.button { background: #333; } } + +@media (prefers-color-scheme: dark) { header h2 a.button { border-color: #444; } } + +header h2 a.button.current { border: 2px solid; background: #fff; border-color: #999; cursor: default; } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { header h2 a.button.current { border-color: #777; } } + +header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } + +header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } + +header.sticky .text { display: none; } + +header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } + +header.sticky .content { padding: 0.5rem 3.5rem; } + +header.sticky .content p { font-size: 1em; } + +header.sticky ~ #source { padding-top: 6.5em; } + +main { position: relative; z-index: 1; } + +footer { margin: 1rem 3.5rem; } + +footer .content { padding: 0; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } + +#index { margin: 1rem 0 0 3.5rem; } + +h1 { font-size: 1.25em; display: inline-block; } + +#filter_container { float: right; margin: 0 2em 0 0; line-height: 1.66em; } + +#filter_container #filter { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { #filter_container #filter { border-color: #444; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #filter_container #filter { color: #eee; } } + +#filter_container #filter:focus { border-color: #007acc; } + +#filter_container :disabled ~ label { color: #ccc; } + +@media (prefers-color-scheme: dark) { #filter_container :disabled ~ label { color: #444; } } + +#filter_container label { font-size: .875em; color: #666; } + +@media (prefers-color-scheme: dark) { #filter_container label { color: #aaa; } } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; background: #eee; color: inherit; text-decoration: none; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { background: #333; } } + +@media (prefers-color-scheme: dark) { header button { border-color: #444; } } + +header button:active, header button:focus { outline: 2px dashed #007acc; } + +header button.run { background: #eeffee; } + +@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } + +header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } + +header button.mis { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } + +header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } + +header button.exc { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } + +header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } + +header button.par { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { header button.par { background: #650; } } + +header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } + +#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } + +#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } + +#help_panel_wrapper { float: right; position: relative; } + +#keyboard_icon { margin: 5px; } + +#help_panel_state { display: none; } + +#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } + +#help_panel .keyhelp p { margin-top: .75em; } + +#help_panel .legend { font-style: italic; margin-bottom: 1em; } + +.indexfile #help_panel { width: 25em; } + +.pyfile #help_panel { width: 18em; } + +#help_panel_state:checked ~ #help_panel { display: block; } + +kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } + +.sep { padding: 0 .1em; } + +#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } + +#source p { position: relative; white-space: pre; } + +#source p * { box-sizing: border-box; } + +#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; user-select: none; } + +@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } + +#source p .n.highlight { background: #ffdd00; } + +#source p .n a { scroll-margin-top: 6em; text-decoration: none; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } + +#source p .n a:hover { text-decoration: underline; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } + +#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } + +@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } + +#source p .t:hover { background: #f2f2f2; } + +@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } + +#source p .t:hover ~ .r .annotate.long { display: block; } + +#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } + +@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } + +#source p .t .key { font-weight: bold; line-height: 1px; } + +#source p .t .str, #source p .t .fst { color: #0451a5; } + +@media (prefers-color-scheme: dark) { #source p .t .str, #source p .t .fst { color: #9cdcfe; } } + +#source p.mis .t { border-left: 0.2em solid #ff0000; } + +#source p.mis.show_mis .t { background: #fdd; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } + +#source p.mis.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } + +#source p.mis.mis2 .t { border-left: 0.2em dotted #ff0000; } + +#source p.mis.mis2.show_mis .t { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t { background: #351b1b; } } + +#source p.mis.mis2.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.mis2.show_mis .t:hover { background: #532323; } } + +#source p.run .t { border-left: 0.2em solid #00dd00; } + +#source p.run.show_run .t { background: #dfd; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } + +#source p.run.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } + +#source p.run.run2 .t { border-left: 0.2em dotted #00dd00; } + +#source p.run.run2.show_run .t { background: #eeffee; } + +@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t { background: #2b2e24; } } + +#source p.run.run2.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.run2.show_run .t:hover { background: #404633; } } + +#source p.exc .t { border-left: 0.2em solid #808080; } + +#source p.exc.show_exc .t { background: #eee; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } + +#source p.exc.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } + +#source p.exc.exc2 .t { border-left: 0.2em dotted #808080; } + +#source p.exc.exc2.show_exc .t { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t { background: #292929; } } + +#source p.exc.exc2.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.exc2.show_exc .t:hover { background: #3c3c3c; } } + +#source p.par .t { border-left: 0.2em solid #bbbb00; } + +#source p.par.show_par .t { background: #ffa; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } + +#source p.par.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } + +#source p.par.par2 .t { border-left: 0.2em dotted #bbbb00; } + +#source p.par.par2.show_par .t { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t { background: #423a0f; } } + +#source p.par.par2.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.par2.show_par .t:hover { background: #6d5d0c; } } + +#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } + +@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } + +#source p .annotate.short:hover ~ .long { display: block; } + +#source p .annotate.long { width: 30em; right: 2.5em; } + +#source p input { display: none; } + +#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } + +#source p input ~ .r label.ctx::before { content: "▶ "; } + +#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } + +#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } + +#source p input:checked ~ .r label.ctx::before { content: "▼ "; } + +#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } + +#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } + +@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } + +#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; text-align: right; } + +@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } + +#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } + +#index table.index { margin-left: -.5em; } + +#index td, #index th { text-align: right; vertical-align: baseline; padding: .25em .5em; border-bottom: 1px solid #eee; } + +@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } + +#index td.name, #index th.name { text-align: left; width: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; min-width: 15em; } + +#index td.left, #index th.left { text-align: left; } + +#index td.spacer, #index th.spacer { border: none; padding: 0; } + +#index td.spacer:hover, #index th.spacer:hover { background: inherit; } + +#index th { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-style: italic; color: #333; border-color: #ccc; cursor: pointer; } + +@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } + +@media (prefers-color-scheme: dark) { #index th { border-color: #444; } } + +#index th:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } + +#index th .arrows { color: #666; font-size: 85%; font-family: sans-serif; font-style: normal; pointer-events: none; } + +#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } + +@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } + +#index th[aria-sort="ascending"] .arrows::after { content: " ▲"; } + +#index th[aria-sort="descending"] .arrows::after { content: " ▼"; } + +#index tr.grouphead th { cursor: default; font-style: normal; border-color: #999; } + +@media (prefers-color-scheme: dark) { #index tr.grouphead th { border-color: #777; } } + +#index td.name { font-size: 1.15em; } + +#index td.name a { text-decoration: none; color: inherit; } + +#index td.name .no-noun { font-style: italic; } + +#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-bottom: none; } + +#index tr.region:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index tr.region:hover { background: #333; } } + +#index tr.region:hover td.name { text-decoration: underline; color: inherit; } + +#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } + +@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } + +#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } + +@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4___init___py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4___init___py.html new file mode 100644 index 0000000000..41921be5cb --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\output\ui\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\ui\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_man_pages_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_man_pages_py.html new file mode 100644 index 0000000000..67557f58f1 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_man_pages_py.html @@ -0,0 +1,147 @@ + + + + + Coverage for httpie\output\ui\man_pages.py: 0% + + + + + +
+
+

+ Coverage for httpie\output\ui\man_pages.py: + 0% +

+ +

+ 16 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1"""Logic for checking and displaying man pages.""" 

+

2 

+

3import subprocess 

+

4import os 

+

5from httpie.context import Environment 

+

6 

+

7 

+

8MAN_COMMAND = 'man' 

+

9NO_MAN_PAGES = os.getenv('HTTPIE_NO_MAN_PAGES', False) 

+

10 

+

11# On some systems, HTTP(n) might exist, but we are only interested in HTTP(1). 

+

12# For more information on man page sections: <https://unix.stackexchange.com/a/138643> 

+

13MAN_PAGE_SECTION = '1' 

+

14 

+

15 

+

16def is_available(program: str) -> bool: 

+

17 """ 

+

18 Check whether `program`'s man pages are available on this system. 

+

19 

+

20 """ 

+

21 if NO_MAN_PAGES or os.system == 'nt': 

+

22 return False 

+

23 try: 

+

24 process = subprocess.run( 

+

25 [MAN_COMMAND, MAN_PAGE_SECTION, program], 

+

26 shell=False, 

+

27 stdout=subprocess.DEVNULL, 

+

28 stderr=subprocess.DEVNULL 

+

29 ) 

+

30 except Exception: 

+

31 # There might be some errors outside the process, e.g 

+

32 # a permission error to execute something that is not an 

+

33 # executable. 

+

34 return False 

+

35 else: 

+

36 return process.returncode == 0 

+

37 

+

38 

+

39def display_for(env: Environment, program: str) -> None: 

+

40 """ 

+

41 Open the system man page for the given command (http/https/httpie). 

+

42 

+

43 """ 

+

44 subprocess.run( 

+

45 [MAN_COMMAND, MAN_PAGE_SECTION, program], 

+

46 stdout=env.stdout, 

+

47 stderr=env.stderr 

+

48 ) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_palette_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_palette_py.html new file mode 100644 index 0000000000..deb0c294e7 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_palette_py.html @@ -0,0 +1,368 @@ + + + + + Coverage for httpie\output\ui\palette.py: 83% + + + + + +
+
+

+ Coverage for httpie\output\ui\palette.py: + 83% +

+ +

+ 72 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from dataclasses import dataclass, field 

+

2from enum import Enum, auto 

+

3from typing import Optional, List 

+

4 

+

5 

+

6PYGMENTS_BRIGHT_BLACK = 'ansibrightblack' 

+

7 

+

8AUTO_STYLE = 'auto' # Follows terminal ANSI color styles 

+

9 

+

10 

+

11class Styles(Enum): 

+

12 PIE = auto() 

+

13 ANSI = auto() 

+

14 

+

15 

+

16class PieStyle(str, Enum): 

+

17 UNIVERSAL = 'pie' 

+

18 DARK = 'pie-dark' 

+

19 LIGHT = 'pie-light' 

+

20 

+

21 

+

22PIE_STYLE_TO_SHADE = { 

+

23 PieStyle.DARK: '500', 

+

24 PieStyle.UNIVERSAL: '600', 

+

25 PieStyle.LIGHT: '700', 

+

26} 

+

27SHADE_TO_PIE_STYLE = { 

+

28 shade: style for style, shade in PIE_STYLE_TO_SHADE.items() 

+

29} 

+

30 

+

31 

+

32class ColorString(str): 

+

33 def __or__(self, other: str) -> 'ColorString': 

+

34 """Combine a style with a property. 

+

35 

+

36 E.g: PieColor.BLUE | BOLD | ITALIC 

+

37 """ 

+

38 if isinstance(other, str): 

+

39 # In case of PieColor.BLUE | SOMETHING 

+

40 # we just create a new string. 

+

41 return ColorString(self + ' ' + other) 

+

42 elif isinstance(other, GenericColor): 42 ↛ 46line 42 didn't jump to line 46 because the condition on line 42 was always true

+

43 # If we see a GenericColor, then we'll wrap it 

+

44 # in with the desired property in a different class. 

+

45 return _StyledGenericColor(other, styles=self.split()) 

+

46 elif isinstance(other, _StyledGenericColor): 

+

47 # And if it is already wrapped, we'll just extend the 

+

48 # list of properties. 

+

49 other.styles.extend(self.split()) 

+

50 return other 

+

51 else: 

+

52 return NotImplemented 

+

53 

+

54 

+

55class PieColor(ColorString, Enum): 

+

56 """Styles that are available only in Pie themes.""" 

+

57 

+

58 PRIMARY = 'primary' 

+

59 SECONDARY = 'secondary' 

+

60 

+

61 WHITE = 'white' 

+

62 BLACK = 'black' 

+

63 GREY = 'grey' 

+

64 AQUA = 'aqua' 

+

65 PURPLE = 'purple' 

+

66 ORANGE = 'orange' 

+

67 RED = 'red' 

+

68 BLUE = 'blue' 

+

69 PINK = 'pink' 

+

70 GREEN = 'green' 

+

71 YELLOW = 'yellow' 

+

72 

+

73 

+

74class GenericColor(Enum): 

+

75 """Generic colors that are safe to use everywhere.""" 

+

76 

+

77 # <https://rich.readthedocs.io/en/stable/appendix/colors.html> 

+

78 

+

79 WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'} 

+

80 BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'} 

+

81 GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'} 

+

82 ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'} 

+

83 YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'} 

+

84 BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'} 

+

85 PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'} 

+

86 PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'} 

+

87 RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'} 

+

88 AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'} 

+

89 GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'} 

+

90 

+

91 def apply_style( 

+

92 self, style: Styles, *, style_name: Optional[str] = None 

+

93 ) -> str: 

+

94 """Apply the given style to a particular value.""" 

+

95 exposed_color = self.value[style] 

+

96 if style is Styles.PIE: 96 ↛ 97line 96 didn't jump to line 97 because the condition on line 96 was never true

+

97 assert style_name is not None 

+

98 shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)] 

+

99 return get_color(exposed_color, shade) 

+

100 else: 

+

101 return exposed_color 

+

102 

+

103 

+

104@dataclass 

+

105class _StyledGenericColor: 

+

106 color: 'GenericColor' 

+

107 styles: List[str] = field(default_factory=list) 

+

108 

+

109 

+

110# noinspection PyDictCreation 

+

111COLOR_PALETTE = { 

+

112 # Copy the brand palette 

+

113 PieColor.WHITE: '#F5F5F0', 

+

114 PieColor.BLACK: '#1C1818', 

+

115 PieColor.GREY: { 

+

116 '50': '#F5F5F0', 

+

117 '100': '#EDEDEB', 

+

118 '200': '#D1D1CF', 

+

119 '300': '#B5B5B2', 

+

120 '400': '#999999', 

+

121 '500': '#7D7D7D', 

+

122 '600': '#666663', 

+

123 '700': '#4F4D4D', 

+

124 '800': '#363636', 

+

125 '900': '#1C1818', 

+

126 'DEFAULT': '#7D7D7D', 

+

127 }, 

+

128 PieColor.AQUA: { 

+

129 '50': '#E8F0F5', 

+

130 '100': '#D6E3ED', 

+

131 '200': '#C4D9E5', 

+

132 '300': '#B0CCDE', 

+

133 '400': '#9EBFD6', 

+

134 '500': '#8CB4CD', 

+

135 '600': '#7A9EB5', 

+

136 '700': '#698799', 

+

137 '800': '#597082', 

+

138 '900': '#455966', 

+

139 'DEFAULT': '#8CB4CD', 

+

140 }, 

+

141 PieColor.PURPLE: { 

+

142 '50': '#F0E0FC', 

+

143 '100': '#E3C7FA', 

+

144 '200': '#D9ADF7', 

+

145 '300': '#CC96F5', 

+

146 '400': '#BF7DF2', 

+

147 '500': '#B464F0', 

+

148 '600': '#9E54D6', 

+

149 '700': '#8745BA', 

+

150 '800': '#70389E', 

+

151 '900': '#5C2982', 

+

152 'DEFAULT': '#B464F0', 

+

153 }, 

+

154 PieColor.ORANGE: { 

+

155 '50': '#FFEDDB', 

+

156 '100': '#FFDEBF', 

+

157 '200': '#FFCFA3', 

+

158 '300': '#FFBF87', 

+

159 '400': '#FFB06B', 

+

160 '500': '#FFA24E', 

+

161 '600': '#F2913D', 

+

162 '700': '#E3822B', 

+

163 '800': '#D6701C', 

+

164 '900': '#C75E0A', 

+

165 'DEFAULT': '#FFA24E', 

+

166 }, 

+

167 PieColor.RED: { 

+

168 '50': '#FFE0DE', 

+

169 '100': '#FFC7C4', 

+

170 '200': '#FFB0AB', 

+

171 '300': '#FF968F', 

+

172 '400': '#FF8075', 

+

173 '500': '#FF665B', 

+

174 '600': '#E34F45', 

+

175 '700': '#C7382E', 

+

176 '800': '#AD2117', 

+

177 '900': '#910A00', 

+

178 'DEFAULT': '#FF665B', 

+

179 }, 

+

180 PieColor.BLUE: { 

+

181 '50': '#DBE3FA', 

+

182 '100': '#BFCFF5', 

+

183 '200': '#A1B8F2', 

+

184 '300': '#85A3ED', 

+

185 '400': '#698FEB', 

+

186 '500': '#4B78E6', 

+

187 '600': '#426BD1', 

+

188 '700': '#3B5EBA', 

+

189 '800': '#3354A6', 

+

190 '900': '#2B478F', 

+

191 'DEFAULT': '#4B78E6', 

+

192 }, 

+

193 PieColor.PINK: { 

+

194 '50': '#FFEBFF', 

+

195 '100': '#FCDBFC', 

+

196 '200': '#FCCCFC', 

+

197 '300': '#FCBAFC', 

+

198 '400': '#FAABFA', 

+

199 '500': '#FA9BFA', 

+

200 '600': '#DE85DE', 

+

201 '700': '#C26EC2', 

+

202 '800': '#A854A6', 

+

203 '900': '#8C3D8A', 

+

204 'DEFAULT': '#FA9BFA', 

+

205 }, 

+

206 PieColor.GREEN: { 

+

207 '50': '#E3F7E8', 

+

208 '100': '#CCF2D6', 

+

209 '200': '#B5EDC4', 

+

210 '300': '#A1E8B0', 

+

211 '400': '#8AE09E', 

+

212 '500': '#73DC8C', 

+

213 '600': '#63C27A', 

+

214 '700': '#52AB66', 

+

215 '800': '#429154', 

+

216 '900': '#307842', 

+

217 'DEFAULT': '#73DC8C', 

+

218 }, 

+

219 PieColor.YELLOW: { 

+

220 '50': '#F7F7DB', 

+

221 '100': '#F2F2BF', 

+

222 '200': '#EDEDA6', 

+

223 '300': '#E5E88A', 

+

224 '400': '#E0E36E', 

+

225 '500': '#DBDE52', 

+

226 '600': '#CCCC3D', 

+

227 '700': '#BABA29', 

+

228 '800': '#ABA614', 

+

229 '900': '#999400', 

+

230 'DEFAULT': '#DBDE52', 

+

231 }, 

+

232} 

+

233COLOR_PALETTE.update( 

+

234 { 

+

235 # Terminal-specific palette customizations. 

+

236 PieColor.GREY: { 

+

237 # Grey is the same no matter shade for the colors 

+

238 shade: COLOR_PALETTE[PieColor.GREY]['500'] 

+

239 for shade in COLOR_PALETTE[PieColor.GREY].keys() 

+

240 }, 

+

241 PieColor.PRIMARY: { 

+

242 '700': COLOR_PALETTE[PieColor.BLACK], 

+

243 '600': PYGMENTS_BRIGHT_BLACK, 

+

244 '500': COLOR_PALETTE[PieColor.WHITE], 

+

245 }, 

+

246 PieColor.SECONDARY: { 

+

247 '700': '#37523C', 

+

248 '600': '#6c6969', 

+

249 '500': '#6c6969', 

+

250 }, 

+

251 } 

+

252) 

+

253 

+

254 

+

255def boldify(color: PieColor) -> str: 

+

256 return f'bold {color}' 

+

257 

+

258 

+

259# noinspection PyDefaultArgument 

+

260def get_color( 

+

261 color: PieColor, shade: str, *, palette=COLOR_PALETTE 

+

262) -> Optional[str]: 

+

263 if color not in palette: 

+

264 return None 

+

265 color_code = palette[color] 

+

266 if isinstance(color_code, dict) and shade in color_code: 266 ↛ 269line 266 didn't jump to line 269 because the condition on line 266 was always true

+

267 return color_code[shade] 

+

268 else: 

+

269 return color_code 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_help_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_help_py.html new file mode 100644 index 0000000000..319e0dcb0f --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_help_py.html @@ -0,0 +1,329 @@ + + + + + Coverage for httpie\output\ui\rich_help.py: 48% + + + + + +
+
+

+ Coverage for httpie\output\ui\rich_help.py: + 48% +

+ +

+ 104 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import re 

+

2import textwrap 

+

3from typing import AbstractSet, Iterable, Optional, Tuple 

+

4 

+

5from rich.console import RenderableType 

+

6from rich.highlighter import RegexHighlighter 

+

7from rich.padding import Padding 

+

8from rich.table import Table 

+

9from rich.text import Text 

+

10 

+

11from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS 

+

12from httpie.cli.options import Argument, ParserSpec, Qualifiers 

+

13from httpie.output.ui.palette import GenericColor 

+

14 

+

15SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS)) 

+

16 

+

17STYLE_METAVAR = GenericColor.YELLOW 

+

18STYLE_SWITCH = GenericColor.GREEN 

+

19STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify() 

+

20STYLE_USAGE_OPTIONAL = GenericColor.GREY 

+

21STYLE_USAGE_REGULAR = GenericColor.WHITE 

+

22STYLE_USAGE_ERROR = GenericColor.RED 

+

23STYLE_USAGE_MISSING = GenericColor.YELLOW 

+

24STYLE_BOLD = 'bold' 

+

25 

+

26MAX_CHOICE_CHARS = 80 

+

27 

+

28LEFT_PADDING_2 = (0, 0, 0, 2) 

+

29LEFT_PADDING_3 = (0, 0, 0, 3) 

+

30LEFT_PADDING_4 = (0, 0, 0, 4) 

+

31LEFT_PADDING_5 = (0, 0, 0, 4) 

+

32 

+

33LEFT_INDENT_2 = (1, 0, 0, 2) 

+

34LEFT_INDENT_3 = (1, 0, 0, 3) 

+

35LEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3) 

+

36 

+

37MORE_INFO_COMMANDS = """ 

+

38To learn more, you can try: 

+

39 -> running 'http --manual' 

+

40 -> visiting our full documentation at https://httpie.io/docs/cli 

+

41""" 

+

42 

+

43 

+

44class OptionsHighlighter(RegexHighlighter): 

+

45 highlights = [ 

+

46 r'(^|\W)(?P<option>\-{1,2}[\w|-]+)(?![a-zA-Z0-9])', 

+

47 r'(?P<bold>HTTPie)', 

+

48 ] 

+

49 

+

50 

+

51options_highlighter = OptionsHighlighter() 

+

52 

+

53 

+

54def unpack_argument( 

+

55 argument: Argument, 

+

56) -> Tuple[Text, Text]: 

+

57 opt1 = opt2 = '' 

+

58 

+

59 style = None 

+

60 if argument.aliases: 

+

61 if len(argument.aliases) >= 2: 

+

62 opt2, opt1 = argument.aliases 

+

63 else: 

+

64 (opt1,) = argument.aliases 

+

65 else: 

+

66 opt1 = argument.metavar 

+

67 style = STYLE_USAGE_REGULAR 

+

68 

+

69 return Text(opt1, style=style), Text(opt2) 

+

70 

+

71 

+

72def to_usage( 

+

73 spec: ParserSpec, 

+

74 *, 

+

75 program_name: Optional[str] = None, 

+

76 whitelist: AbstractSet[str] = frozenset() 

+

77) -> RenderableType: 

+

78 shown_arguments = [ 

+

79 argument 

+

80 for group in spec.groups 

+

81 for argument in group.arguments 

+

82 if (not argument.aliases or whitelist.intersection(argument.aliases)) 

+

83 ] 

+

84 

+

85 # Sort the shown_arguments so that --dash options are 

+

86 # shown first 

+

87 shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True) 

+

88 

+

89 text = Text(program_name or spec.program, style=STYLE_BOLD) 

+

90 for argument in shown_arguments: 

+

91 text.append(' ') 

+

92 

+

93 is_whitelisted = whitelist.intersection(argument.aliases) 

+

94 if argument.aliases: 

+

95 name = '/'.join(sorted(argument.aliases, key=len)) 

+

96 else: 

+

97 name = argument.metavar 

+

98 

+

99 nargs = argument.configuration.get('nargs') 

+

100 if nargs is Qualifiers.OPTIONAL: 

+

101 text.append('[' + name + ']', style=STYLE_USAGE_OPTIONAL) 

+

102 elif nargs is Qualifiers.ZERO_OR_MORE: 

+

103 text.append( 

+

104 '[' + name + ' ...]', 

+

105 style=STYLE_USAGE_OPTIONAL, 

+

106 ) 

+

107 else: 

+

108 text.append( 

+

109 name, 

+

110 style=STYLE_USAGE_ERROR 

+

111 if is_whitelisted 

+

112 else STYLE_USAGE_REGULAR, 

+

113 ) 

+

114 

+

115 raw_form = argument.serialize() 

+

116 if raw_form.get('choices'): 

+

117 text.append(' ') 

+

118 text.append( 

+

119 '{' + ', '.join(raw_form['choices']) + '}', 

+

120 style=STYLE_USAGE_MISSING, 

+

121 ) 

+

122 

+

123 return text 

+

124 

+

125 

+

126# This part is loosely based on the rich-click's help message 

+

127# generation. 

+

128def to_help_message( 

+

129 spec: ParserSpec, 

+

130) -> Iterable[RenderableType]: 

+

131 yield Padding( 

+

132 options_highlighter(spec.description), 

+

133 LEFT_INDENT_2, 

+

134 ) 

+

135 

+

136 yield Padding( 

+

137 Text('Usage', style=STYLE_SWITCH), 

+

138 LEFT_INDENT_2, 

+

139 ) 

+

140 yield Padding(to_usage(spec), LEFT_INDENT_3) 

+

141 

+

142 group_rows = {} 

+

143 for group in spec.groups: 

+

144 options_rows = [] 

+

145 

+

146 for argument in group.arguments: 

+

147 if argument.is_hidden: 

+

148 continue 

+

149 

+

150 opt1, opt2 = unpack_argument(argument) 

+

151 if opt2: 

+

152 opt1.append('/') 

+

153 opt1.append(opt2) 

+

154 

+

155 # Column for a metavar, if we have one 

+

156 metavar = Text(style=STYLE_METAVAR) 

+

157 metavar.append(argument.configuration.get('metavar', '')) 

+

158 

+

159 if opt1 == metavar: 

+

160 metavar = Text('') 

+

161 

+

162 raw_form = argument.serialize() 

+

163 desc = raw_form.get('short_description', '') 

+

164 if raw_form.get('choices'): 

+

165 desc += ' (choices: ' 

+

166 desc += textwrap.shorten( 

+

167 ', '.join(raw_form.get('choices')), 

+

168 MAX_CHOICE_CHARS, 

+

169 ) 

+

170 desc += ')' 

+

171 

+

172 rows = [ 

+

173 Padding( 

+

174 options_highlighter(opt1), 

+

175 LEFT_PADDING_2, 

+

176 ), 

+

177 metavar, 

+

178 options_highlighter(desc), 

+

179 ] 

+

180 

+

181 options_rows.append(rows) 

+

182 if argument.configuration.get('nested_options'): 

+

183 options_rows.extend( 

+

184 [ 

+

185 ( 

+

186 Padding( 

+

187 Text( 

+

188 key, 

+

189 style=STYLE_USAGE_OPTIONAL, 

+

190 ), 

+

191 LEFT_PADDING_4, 

+

192 ), 

+

193 value, 

+

194 dec, 

+

195 ) 

+

196 for key, value, dec in argument.nested_options 

+

197 ] 

+

198 ) 

+

199 

+

200 group_rows[group.name] = options_rows 

+

201 

+

202 options_table = Table(highlight=False, box=None, show_header=False) 

+

203 for group_name, options_rows in group_rows.items(): 

+

204 options_table.add_row(Text(), Text(), Text()) 

+

205 options_table.add_row( 

+

206 Text(group_name, style=STYLE_SWITCH), 

+

207 Text(), 

+

208 Text(), 

+

209 ) 

+

210 options_table.add_row(Text(), Text(), Text()) 

+

211 for row in options_rows: 

+

212 options_table.add_row(*row) 

+

213 

+

214 yield Padding( 

+

215 Text('Options', style=STYLE_SWITCH), 

+

216 LEFT_INDENT_2, 

+

217 ) 

+

218 yield Padding(options_table, LEFT_PADDING_2) 

+

219 yield Padding( 

+

220 Text('More Information', style=STYLE_SWITCH), 

+

221 LEFT_INDENT_2, 

+

222 ) 

+

223 yield Padding( 

+

224 MORE_INFO_COMMANDS.rstrip('\n'), 

+

225 LEFT_PADDING_3 

+

226 ) 

+

227 yield Padding( 

+

228 spec.epilog.rstrip('\n'), 

+

229 LEFT_INDENT_BOTTOM_3, 

+

230 ) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_palette_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_palette_py.html new file mode 100644 index 0000000000..58a341a5e2 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_palette_py.html @@ -0,0 +1,172 @@ + + + + + Coverage for httpie\output\ui\rich_palette.py: 95% + + + + + +
+
+

+ Coverage for httpie\output\ui\rich_palette.py: + 95% +

+ +

+ 31 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from collections import ChainMap 

+

2from typing import TYPE_CHECKING, Any, Optional 

+

3 

+

4if TYPE_CHECKING: 

+

5 from rich.theme import Theme 

+

6 

+

7from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa 

+

8 

+

9RICH_BOLD = ColorString('bold') 

+

10 

+

11# Rich-specific color code declarations 

+

12# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py> 

+

13CUSTOM_STYLES = { 

+

14 'progress.description': RICH_BOLD | GenericColor.WHITE, 

+

15 'progress.data.speed': RICH_BOLD | GenericColor.GREEN, 

+

16 'progress.percentage': RICH_BOLD | GenericColor.AQUA, 

+

17 'progress.download': RICH_BOLD | GenericColor.AQUA, 

+

18 'progress.remaining': RICH_BOLD | GenericColor.ORANGE, 

+

19 'bar.complete': RICH_BOLD | GenericColor.PURPLE, 

+

20 'bar.finished': RICH_BOLD | GenericColor.GREEN, 

+

21 'bar.pulse': RICH_BOLD | GenericColor.PURPLE, 

+

22 'option': RICH_BOLD | GenericColor.PINK, 

+

23} 

+

24 

+

25 

+

26class _GenericColorCaster(dict): 

+

27 """ 

+

28 Translate GenericColor to a regular string on the attribute access 

+

29 phase. 

+

30 """ 

+

31 

+

32 def _translate(self, key: Any) -> Any: 

+

33 if isinstance(key, GenericColor): 

+

34 return key.name.lower() 

+

35 else: 

+

36 return key 

+

37 

+

38 def __getitem__(self, key: Any) -> Any: 

+

39 return super().__getitem__(self._translate(key)) 

+

40 

+

41 def get(self, key: Any) -> Any: 

+

42 return super().get(self._translate(key)) 

+

43 

+

44 

+

45def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme': 

+

46 from rich.style import Style 

+

47 from rich.theme import Theme 

+

48 

+

49 try: 

+

50 PieStyle(style_name) 

+

51 except ValueError: 

+

52 style = Styles.ANSI 

+

53 else: 

+

54 style = Styles.PIE 

+

55 

+

56 theme = Theme() 

+

57 for color, color_set in ChainMap( 

+

58 GenericColor.__members__, CUSTOM_STYLES 

+

59 ).items(): 

+

60 if isinstance(color_set, _StyledGenericColor): 

+

61 properties = dict.fromkeys(color_set.styles, True) 

+

62 color_set = color_set.color 

+

63 else: 

+

64 properties = {} 

+

65 

+

66 theme.styles[color.lower()] = Style( 

+

67 color=color_set.apply_style(style, style_name=style_name), 

+

68 **properties, 

+

69 ) 

+

70 

+

71 # E.g translate GenericColor.BLUE into blue on key access 

+

72 theme.styles = _GenericColorCaster(theme.styles) 

+

73 return theme 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_progress_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_progress_py.html new file mode 100644 index 0000000000..85d779876d --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_progress_py.html @@ -0,0 +1,240 @@ + + + + + Coverage for httpie\output\ui\rich_progress.py: 71% + + + + + +
+
+

+ Coverage for httpie\output\ui\rich_progress.py: + 71% +

+ +

+ 54 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from dataclasses import dataclass 

+

2from typing import TYPE_CHECKING, Optional 

+

3 

+

4from httpie.context import Environment 

+

5 

+

6if TYPE_CHECKING: 

+

7 from rich.console import Console 

+

8 

+

9 

+

10@dataclass 

+

11class BaseDisplay: 

+

12 env: Environment 

+

13 

+

14 def start( 

+

15 self, *, total: Optional[float], at: float, description: str 

+

16 ) -> None: 

+

17 ... 

+

18 

+

19 def update(self, steps: float) -> None: 

+

20 ... 

+

21 

+

22 def stop(self, time_spent: float) -> None: 

+

23 ... 

+

24 

+

25 @property 

+

26 def console(self) -> 'Console': 

+

27 """Returns the default console to be used with displays (stderr).""" 

+

28 return self.env.rich_error_console 

+

29 

+

30 def _print_summary( 

+

31 self, is_finished: bool, observed_steps: int, time_spent: float 

+

32 ): 

+

33 from rich import filesize 

+

34 

+

35 if is_finished: 35 ↛ 38line 35 didn't jump to line 38 because the condition on line 35 was always true

+

36 verb = 'Done' 

+

37 else: 

+

38 verb = 'Interrupted' 

+

39 

+

40 total_size = filesize.decimal(observed_steps) 

+

41 avg_speed = filesize.decimal(observed_steps / time_spent) 

+

42 

+

43 minutes, seconds = divmod(time_spent, 60) 

+

44 hours, minutes = divmod(int(minutes), 60) 

+

45 if hours: 45 ↛ 46line 45 didn't jump to line 46 because the condition on line 45 was never true

+

46 total_time = f'{hours:d}:{minutes:02d}:{seconds:0.5f}' 

+

47 else: 

+

48 total_time = f'{minutes:02d}:{seconds:0.5f}' 

+

49 

+

50 self.console.print( 

+

51 f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)' 

+

52 ) 

+

53 

+

54 

+

55class DummyDisplay(BaseDisplay): 

+

56 """ 

+

57 A dummy display object to be used when the progress bars, 

+

58 spinners etc. are disabled globally (or during tests). 

+

59 """ 

+

60 

+

61 

+

62class StatusDisplay(BaseDisplay): 

+

63 def start( 

+

64 self, *, total: Optional[float], at: float, description: str 

+

65 ) -> None: 

+

66 self.observed = at 

+

67 self.description = ( 

+

68 f'[progress.description]{description}[/progress.description]' 

+

69 ) 

+

70 

+

71 self.status = self.console.status(self.description, spinner='line') 

+

72 self.status.start() 

+

73 

+

74 def update(self, steps: float) -> None: 

+

75 from rich import filesize 

+

76 

+

77 self.observed += steps 

+

78 

+

79 observed_amount, observed_unit = filesize.decimal( 

+

80 self.observed 

+

81 ).split() 

+

82 self.status.update( 

+

83 status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]' 

+

84 ) 

+

85 

+

86 def stop(self, time_spent: float) -> None: 

+

87 self.status.stop() 

+

88 self.console.print(self.description) 

+

89 if time_spent: 

+

90 self._print_summary( 

+

91 is_finished=True, 

+

92 observed_steps=self.observed, 

+

93 time_spent=time_spent, 

+

94 ) 

+

95 

+

96 

+

97class ProgressDisplay(BaseDisplay): 

+

98 def start( 

+

99 self, *, total: Optional[float], at: float, description: str 

+

100 ) -> None: 

+

101 from rich.progress import ( 

+

102 Progress, 

+

103 BarColumn, 

+

104 DownloadColumn, 

+

105 TimeRemainingColumn, 

+

106 TransferSpeedColumn, 

+

107 ) 

+

108 

+

109 assert total is not None 

+

110 self.console.print(f'[progress.description]{description}') 

+

111 self.progress_bar = Progress( 

+

112 '[', 

+

113 BarColumn(), 

+

114 ']', 

+

115 '[progress.percentage]{task.percentage:>3.0f}%', 

+

116 '(', 

+

117 DownloadColumn(), 

+

118 ')', 

+

119 TimeRemainingColumn(), 

+

120 TransferSpeedColumn(), 

+

121 console=self.console, 

+

122 transient=True, 

+

123 ) 

+

124 self.progress_bar.start() 

+

125 self.transfer_task = self.progress_bar.add_task( 

+

126 description, completed=at, total=total 

+

127 ) 

+

128 

+

129 def update(self, steps: float) -> None: 

+

130 self.progress_bar.advance(self.transfer_task, steps) 

+

131 

+

132 def stop(self, time_spent: Optional[float]) -> None: 

+

133 self.progress_bar.stop() 

+

134 

+

135 if time_spent: 

+

136 [task] = self.progress_bar.tasks 

+

137 self._print_summary( 

+

138 is_finished=task.finished, 

+

139 observed_steps=task.completed, 

+

140 time_spent=time_spent, 

+

141 ) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_utils_py.html b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_utils_py.html new file mode 100644 index 0000000000..e5e85147b9 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_25e3941e266507d4_rich_utils_py.html @@ -0,0 +1,133 @@ + + + + + Coverage for httpie\output\ui\rich_utils.py: 0% + + + + + +
+
+

+ Coverage for httpie\output\ui\rich_utils.py: + 0% +

+ +

+ 18 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import os 

+

2 

+

3from typing import Iterator 

+

4from contextlib import contextmanager 

+

5 

+

6from rich.console import Console, RenderableType 

+

7from rich.highlighter import Highlighter 

+

8 

+

9from httpie.output.ui.rich_palette import _make_rich_color_theme 

+

10 

+

11 

+

12def render_as_string(renderable: RenderableType) -> str: 

+

13 """Render any `rich` object in a fake console and 

+

14 return a *style-less* version of it as a string.""" 

+

15 

+

16 with open(os.devnull, 'w') as null_stream: 

+

17 fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme()) 

+

18 fake_console.print(renderable) 

+

19 return fake_console.export_text() 

+

20 

+

21 

+

22@contextmanager 

+

23def enable_highlighter( 

+

24 console: Console, 

+

25 highlighter: Highlighter, 

+

26) -> Iterator[Console]: 

+

27 """Enable a highlighter temporarily.""" 

+

28 

+

29 original_highlighter = console.highlighter 

+

30 try: 

+

31 console.highlighter = highlighter 

+

32 yield console 

+

33 finally: 

+

34 console.highlighter = original_highlighter 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a___init___py.html b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a___init___py.html new file mode 100644 index 0000000000..c81fab1426 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\output\formatters\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\formatters\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_colors_py.html b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_colors_py.html new file mode 100644 index 0000000000..a6ecb3b93c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_colors_py.html @@ -0,0 +1,487 @@ + + + + + Coverage for httpie\output\formatters\colors.py: 99% + + + + + +
+
+

+ Coverage for httpie\output\formatters\colors.py: + 99% +

+ +

+ 140 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import json 

+

2from typing import Optional, Type, Tuple 

+

3 

+

4import pygments.formatters 

+

5import pygments.lexer 

+

6import pygments.lexers 

+

7import pygments.style 

+

8import pygments.styles 

+

9import pygments.token 

+

10from pygments.formatters.terminal import TerminalFormatter 

+

11from pygments.formatters.terminal256 import Terminal256Formatter 

+

12from pygments.lexer import Lexer 

+

13from pygments.lexers.data import JsonLexer 

+

14from pygments.lexers.special import TextLexer 

+

15from pygments.lexers.text import HttpLexer as PygmentsHttpLexer 

+

16from pygments.util import ClassNotFound 

+

17 

+

18from ..lexers.json import EnhancedJsonLexer 

+

19from ..lexers.metadata import MetadataLexer 

+

20from ..ui.palette import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieColor, ColorString, get_color 

+

21from ...context import Environment 

+

22from ...plugins import FormatterPlugin 

+

23 

+

24 

+

25DEFAULT_STYLE = AUTO_STYLE 

+

26SOLARIZED_STYLE = 'solarized' # Bundled here 

+

27PYGMENTS_BOLD = ColorString('bold') 

+

28PYGMENTS_ITALIC = ColorString('italic') 

+

29 

+

30BUNDLED_STYLES = { 

+

31 SOLARIZED_STYLE, 

+

32 AUTO_STYLE 

+

33} 

+

34 

+

35 

+

36def get_available_styles(): 

+

37 return sorted(BUNDLED_STYLES | set(pygments.styles.get_all_styles())) 

+

38 

+

39 

+

40class ColorFormatter(FormatterPlugin): 

+

41 """ 

+

42 Colorize using Pygments 

+

43 

+

44 This processor that applies syntax highlighting to the headers, 

+

45 and also to the body if its content type is recognized. 

+

46 

+

47 """ 

+

48 group_name = 'colors' 

+

49 metadata_lexer = MetadataLexer() 

+

50 

+

51 def __init__( 

+

52 self, 

+

53 env: Environment, 

+

54 explicit_json=False, 

+

55 color_scheme=DEFAULT_STYLE, 

+

56 **kwargs 

+

57 ): 

+

58 super().__init__(**kwargs) 

+

59 

+

60 if not env.colors: 

+

61 self.enabled = False 

+

62 return 

+

63 

+

64 use_auto_style = color_scheme == AUTO_STYLE 

+

65 has_256_colors = env.colors == 256 

+

66 if use_auto_style or not has_256_colors: 

+

67 http_lexer = PygmentsHttpLexer() 

+

68 body_formatter = header_formatter = TerminalFormatter() 

+

69 precise = False 

+

70 else: 

+

71 from ..lexers.http import SimplifiedHTTPLexer 

+

72 header_formatter, body_formatter, precise = self.get_formatters(color_scheme) 

+

73 http_lexer = SimplifiedHTTPLexer(precise=precise) 

+

74 

+

75 self.explicit_json = explicit_json # --json 

+

76 self.header_formatter = header_formatter 

+

77 self.body_formatter = body_formatter 

+

78 self.http_lexer = http_lexer 

+

79 self.metadata_lexer = MetadataLexer(precise=precise) 

+

80 

+

81 def format_headers(self, headers: str) -> str: 

+

82 return pygments.highlight( 

+

83 code=headers, 

+

84 lexer=self.http_lexer, 

+

85 formatter=self.header_formatter, 

+

86 ).strip() 

+

87 

+

88 def format_body(self, body: str, mime: str) -> str: 

+

89 lexer = self.get_lexer_for_body(mime, body) 

+

90 if lexer: 90 ↛ 96line 90 didn't jump to line 96 because the condition on line 90 was always true

+

91 body = pygments.highlight( 

+

92 code=body, 

+

93 lexer=lexer, 

+

94 formatter=self.body_formatter, 

+

95 ) 

+

96 return body 

+

97 

+

98 def format_metadata(self, metadata: str) -> str: 

+

99 return pygments.highlight( 

+

100 code=metadata, 

+

101 lexer=self.metadata_lexer, 

+

102 formatter=self.header_formatter, 

+

103 ).strip() 

+

104 

+

105 def get_lexer_for_body( 

+

106 self, mime: str, 

+

107 body: str 

+

108 ) -> Optional[Type[Lexer]]: 

+

109 return get_lexer( 

+

110 mime=mime, 

+

111 explicit_json=self.explicit_json, 

+

112 body=body, 

+

113 ) 

+

114 

+

115 def get_formatters(self, color_scheme: str) -> Tuple[ 

+

116 pygments.formatter.Formatter, 

+

117 pygments.formatter.Formatter, 

+

118 bool 

+

119 ]: 

+

120 if color_scheme in PIE_STYLES: 

+

121 header_style, body_style = PIE_STYLES[color_scheme] 

+

122 precise = True 

+

123 else: 

+

124 header_style = self.get_style_class(color_scheme) 

+

125 body_style = header_style 

+

126 precise = False 

+

127 

+

128 return ( 

+

129 Terminal256Formatter(style=header_style), 

+

130 Terminal256Formatter(style=body_style), 

+

131 precise 

+

132 ) 

+

133 

+

134 @staticmethod 

+

135 def get_style_class(color_scheme: str) -> Type[pygments.style.Style]: 

+

136 try: 

+

137 return pygments.styles.get_style_by_name(color_scheme) 

+

138 except ClassNotFound: 

+

139 return Solarized256Style 

+

140 

+

141 

+

142def get_lexer( 

+

143 mime: str, 

+

144 explicit_json=False, 

+

145 body='' 

+

146) -> Optional[Type[Lexer]]: 

+

147 # Build candidate mime type and lexer names. 

+

148 mime_types, lexer_names = [mime], [] 

+

149 type_, subtype = mime.split('/', 1) 

+

150 if '+' not in subtype: 

+

151 lexer_names.append(subtype) 

+

152 else: 

+

153 subtype_name, subtype_suffix = subtype.split('+', 1) 

+

154 lexer_names.extend([subtype_name, subtype_suffix]) 

+

155 mime_types.extend([ 

+

156 f'{type_}/{subtype_name}', 

+

157 f'{type_}/{subtype_suffix}', 

+

158 ]) 

+

159 

+

160 # As a last resort, if no lexer feels responsible, and 

+

161 # the subtype contains 'json', take the JSON lexer 

+

162 if 'json' in subtype: 

+

163 lexer_names.append('json') 

+

164 

+

165 # Try to resolve the right lexer. 

+

166 lexer = None 

+

167 for mime_type in mime_types: 

+

168 try: 

+

169 lexer = pygments.lexers.get_lexer_for_mimetype(mime_type) 

+

170 break 

+

171 except ClassNotFound: 

+

172 pass 

+

173 else: 

+

174 for name in lexer_names: 

+

175 try: 

+

176 lexer = pygments.lexers.get_lexer_by_name(name) 

+

177 except ClassNotFound: 

+

178 pass 

+

179 

+

180 if explicit_json and body and (not lexer or isinstance(lexer, TextLexer)): 

+

181 # JSON response with an incorrect Content-Type? 

+

182 try: 

+

183 json.loads(body) # FIXME: the body also gets parsed in json.py 

+

184 except ValueError: 

+

185 pass # Nope 

+

186 else: 

+

187 lexer = pygments.lexers.get_lexer_by_name('json') 

+

188 

+

189 # Use our own JSON lexer: it supports JSON bodies preceded by non-JSON data 

+

190 # as well as legit JSON bodies. 

+

191 if isinstance(lexer, JsonLexer): 

+

192 lexer = EnhancedJsonLexer() 

+

193 

+

194 return lexer 

+

195 

+

196 

+

197class Solarized256Style(pygments.style.Style): 

+

198 """ 

+

199 solarized256 

+

200 ------------ 

+

201 

+

202 A Pygments style inspired by Solarized's 256 color mode. 

+

203 

+

204 :copyright: (c) 2011 by Hank Gay, (c) 2012 by John Mastro. 

+

205 :license: BSD, see LICENSE for more details. 

+

206 

+

207 """ 

+

208 BASE03 = "#1c1c1c" 

+

209 BASE02 = "#262626" 

+

210 BASE01 = "#4e4e4e" 

+

211 BASE00 = "#585858" 

+

212 BASE0 = "#808080" 

+

213 BASE1 = "#8a8a8a" 

+

214 BASE2 = "#d7d7af" 

+

215 BASE3 = "#ffffd7" 

+

216 YELLOW = "#af8700" 

+

217 ORANGE = "#d75f00" 

+

218 RED = "#af0000" 

+

219 MAGENTA = "#af005f" 

+

220 VIOLET = "#5f5faf" 

+

221 BLUE = "#0087ff" 

+

222 CYAN = "#00afaf" 

+

223 GREEN = "#5f8700" 

+

224 

+

225 background_color = BASE03 

+

226 styles = { 

+

227 pygments.token.Keyword: GREEN, 

+

228 pygments.token.Keyword.Constant: ORANGE, 

+

229 pygments.token.Keyword.Declaration: BLUE, 

+

230 pygments.token.Keyword.Namespace: ORANGE, 

+

231 pygments.token.Keyword.Reserved: BLUE, 

+

232 pygments.token.Keyword.Type: RED, 

+

233 pygments.token.Name.Attribute: BASE1, 

+

234 pygments.token.Name.Builtin: BLUE, 

+

235 pygments.token.Name.Builtin.Pseudo: BLUE, 

+

236 pygments.token.Name.Class: BLUE, 

+

237 pygments.token.Name.Constant: ORANGE, 

+

238 pygments.token.Name.Decorator: BLUE, 

+

239 pygments.token.Name.Entity: ORANGE, 

+

240 pygments.token.Name.Exception: YELLOW, 

+

241 pygments.token.Name.Function: BLUE, 

+

242 pygments.token.Name.Tag: BLUE, 

+

243 pygments.token.Name.Variable: BLUE, 

+

244 pygments.token.String: CYAN, 

+

245 pygments.token.String.Backtick: BASE01, 

+

246 pygments.token.String.Char: CYAN, 

+

247 pygments.token.String.Doc: CYAN, 

+

248 pygments.token.String.Escape: RED, 

+

249 pygments.token.String.Heredoc: CYAN, 

+

250 pygments.token.String.Regex: RED, 

+

251 pygments.token.Number: CYAN, 

+

252 pygments.token.Operator: BASE1, 

+

253 pygments.token.Operator.Word: GREEN, 

+

254 pygments.token.Comment: BASE01, 

+

255 pygments.token.Comment.Preproc: GREEN, 

+

256 pygments.token.Comment.Special: GREEN, 

+

257 pygments.token.Generic.Deleted: CYAN, 

+

258 pygments.token.Generic.Emph: PYGMENTS_ITALIC, 

+

259 pygments.token.Generic.Error: RED, 

+

260 pygments.token.Generic.Heading: ORANGE, 

+

261 pygments.token.Generic.Inserted: GREEN, 

+

262 pygments.token.Generic.Strong: PYGMENTS_BOLD, 

+

263 pygments.token.Generic.Subheading: ORANGE, 

+

264 pygments.token.Token: BASE1, 

+

265 pygments.token.Token.Other: ORANGE, 

+

266 } 

+

267 

+

268 

+

269PIE_HEADER_STYLE = { 

+

270 # HTTP line / Headers / Etc. 

+

271 pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY, 

+

272 pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY, 

+

273 pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY, 

+

274 pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY, 

+

275 pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN, 

+

276 pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN, 

+

277 pygments.token.Name.Attribute: PieColor.BLUE, 

+

278 pygments.token.String: PieColor.PRIMARY, 

+

279 

+

280 # HTTP Methods 

+

281 pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY, 

+

282 pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN, 

+

283 pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN, 

+

284 pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW, 

+

285 pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE, 

+

286 pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE, 

+

287 pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED, 

+

288 

+

289 # HTTP status codes 

+

290 pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA, 

+

291 pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN, 

+

292 pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW, 

+

293 pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE, 

+

294 pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED, 

+

295 

+

296 # Metadata 

+

297 pygments.token.Name.Decorator: PieColor.GREY, 

+

298 pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN, 

+

299 pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW, 

+

300 pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE, 

+

301 pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED, 

+

302} 

+

303 

+

304PIE_BODY_STYLE = { 

+

305 # {}[]: 

+

306 pygments.token.Punctuation: PieColor.GREY, 

+

307 

+

308 # Keys 

+

309 pygments.token.Name.Tag: PieColor.PINK, 

+

310 

+

311 # Values 

+

312 pygments.token.Literal.String: PieColor.GREEN, 

+

313 pygments.token.Literal.String.Double: PieColor.GREEN, 

+

314 pygments.token.Literal.Number: PieColor.AQUA, 

+

315 pygments.token.Keyword: PieColor.ORANGE, 

+

316 

+

317 # Other stuff 

+

318 pygments.token.Text: PieColor.PRIMARY, 

+

319 pygments.token.Name.Attribute: PieColor.PRIMARY, 

+

320 pygments.token.Name.Builtin: PieColor.BLUE, 

+

321 pygments.token.Name.Builtin.Pseudo: PieColor.BLUE, 

+

322 pygments.token.Name.Class: PieColor.BLUE, 

+

323 pygments.token.Name.Constant: PieColor.ORANGE, 

+

324 pygments.token.Name.Decorator: PieColor.BLUE, 

+

325 pygments.token.Name.Entity: PieColor.ORANGE, 

+

326 pygments.token.Name.Exception: PieColor.YELLOW, 

+

327 pygments.token.Name.Function: PieColor.BLUE, 

+

328 pygments.token.Name.Variable: PieColor.BLUE, 

+

329 pygments.token.String: PieColor.AQUA, 

+

330 pygments.token.String.Backtick: PieColor.SECONDARY, 

+

331 pygments.token.String.Char: PieColor.AQUA, 

+

332 pygments.token.String.Doc: PieColor.AQUA, 

+

333 pygments.token.String.Escape: PieColor.RED, 

+

334 pygments.token.String.Heredoc: PieColor.AQUA, 

+

335 pygments.token.String.Regex: PieColor.RED, 

+

336 pygments.token.Number: PieColor.AQUA, 

+

337 pygments.token.Operator: PieColor.PRIMARY, 

+

338 pygments.token.Operator.Word: PieColor.GREEN, 

+

339 pygments.token.Comment: PieColor.SECONDARY, 

+

340 pygments.token.Comment.Preproc: PieColor.GREEN, 

+

341 pygments.token.Comment.Special: PieColor.GREEN, 

+

342 pygments.token.Generic.Deleted: PieColor.AQUA, 

+

343 pygments.token.Generic.Emph: PYGMENTS_ITALIC, 

+

344 pygments.token.Generic.Error: PieColor.RED, 

+

345 pygments.token.Generic.Heading: PieColor.ORANGE, 

+

346 pygments.token.Generic.Inserted: PieColor.GREEN, 

+

347 pygments.token.Generic.Strong: PYGMENTS_BOLD, 

+

348 pygments.token.Generic.Subheading: PieColor.ORANGE, 

+

349 pygments.token.Token: PieColor.PRIMARY, 

+

350 pygments.token.Token.Other: PieColor.ORANGE, 

+

351} 

+

352 

+

353 

+

354def make_style(name, raw_styles, shade): 

+

355 def format_value(value): 

+

356 return ' '.join( 

+

357 get_color(part, shade) or part 

+

358 for part in value.split() 

+

359 ) 

+

360 

+

361 bases = (pygments.style.Style,) 

+

362 data = { 

+

363 'styles': { 

+

364 key: format_value(value) 

+

365 for key, value in raw_styles.items() 

+

366 } 

+

367 } 

+

368 return type(name, bases, data) 

+

369 

+

370 

+

371def make_styles(): 

+

372 styles = {} 

+

373 

+

374 for shade, name in SHADE_TO_PIE_STYLE.items(): 

+

375 styles[name] = [ 

+

376 make_style(name, style_map, shade) 

+

377 for style_name, style_map in [ 

+

378 (f'Pie{name}HeaderStyle', PIE_HEADER_STYLE), 

+

379 (f'Pie{name}BodyStyle', PIE_BODY_STYLE), 

+

380 ] 

+

381 ] 

+

382 

+

383 return styles 

+

384 

+

385 

+

386PIE_STYLES = make_styles() 

+

387PIE_STYLE_NAMES = list(PIE_STYLES.keys()) 

+

388BUNDLED_STYLES |= PIE_STYLES.keys() 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_headers_py.html b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_headers_py.html new file mode 100644 index 0000000000..a3c568d8b0 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_headers_py.html @@ -0,0 +1,117 @@ + + + + + Coverage for httpie\output\formatters\headers.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\formatters\headers.py: + 100% +

+ +

+ 9 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from ...plugins import FormatterPlugin 

+

2 

+

3 

+

4class HeadersFormatter(FormatterPlugin): 

+

5 

+

6 def __init__(self, **kwargs): 

+

7 super().__init__(**kwargs) 

+

8 self.enabled = self.format_options['headers']['sort'] 

+

9 

+

10 def format_headers(self, headers: str) -> str: 

+

11 """ 

+

12 Sorts headers by name while retaining relative 

+

13 order of multiple headers with the same name. 

+

14 

+

15 """ 

+

16 lines = headers.splitlines() 

+

17 headers = sorted(lines[1:], key=lambda h: h.split(':')[0]) 

+

18 return '\r\n'.join(lines[:1] + headers) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_json_py.html b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_json_py.html new file mode 100644 index 0000000000..aab478a79a --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_json_py.html @@ -0,0 +1,133 @@ + + + + + Coverage for httpie\output\formatters\json.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\formatters\json.py: + 100% +

+ +

+ 16 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import json 

+

2 

+

3from ...plugins import FormatterPlugin 

+

4 

+

5 

+

6class JSONFormatter(FormatterPlugin): 

+

7 

+

8 def __init__(self, **kwargs): 

+

9 super().__init__(**kwargs) 

+

10 self.enabled = self.format_options['json']['format'] 

+

11 

+

12 def format_body(self, body: str, mime: str) -> str: 

+

13 maybe_json = [ 

+

14 'json', 

+

15 'javascript', 

+

16 'text', 

+

17 ] 

+

18 if (self.kwargs['explicit_json'] 

+

19 or any(token in mime for token in maybe_json)): 

+

20 from ..utils import load_prefixed_json 

+

21 try: 

+

22 data_prefix, json_obj = load_prefixed_json(body) 

+

23 except ValueError: 

+

24 pass # Invalid JSON, ignore. 

+

25 else: 

+

26 # Indent, sort keys by name, and avoid 

+

27 # unicode escapes to improve readability. 

+

28 body = data_prefix + json.dumps( 

+

29 obj=json_obj, 

+

30 sort_keys=self.format_options['json']['sort_keys'], 

+

31 ensure_ascii=False, 

+

32 indent=self.format_options['json']['indent'] 

+

33 ) 

+

34 return body 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_xml_py.html b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_xml_py.html new file mode 100644 index 0000000000..0396431b0a --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_35ca103ad7845a7a_xml_py.html @@ -0,0 +1,178 @@ + + + + + Coverage for httpie\output\formatters\xml.py: 96% + + + + + +
+
+

+ Coverage for httpie\output\formatters\xml.py: + 96% +

+ +

+ 41 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import TYPE_CHECKING, Optional 

+

2 

+

3from ...encoding import UTF8 

+

4from ...plugins import FormatterPlugin 

+

5 

+

6if TYPE_CHECKING: 

+

7 from xml.dom.minidom import Document 

+

8 

+

9 

+

10XML_DECLARATION_OPEN = '<?xml' 

+

11XML_DECLARATION_CLOSE = '?>' 

+

12 

+

13 

+

14def parse_xml(data: str) -> 'Document': 

+

15 """Parse given XML `data` string into an appropriate :class:`~xml.dom.minidom.Document` object.""" 

+

16 from defusedxml.minidom import parseString 

+

17 return parseString(data) 

+

18 

+

19 

+

20def parse_declaration(raw_body: str) -> Optional[str]: 

+

21 body = raw_body.strip() 

+

22 # XMLDecl ::= '<?xml' DECL_CONTENT '?>' 

+

23 if body.startswith(XML_DECLARATION_OPEN): 

+

24 end = body.find(XML_DECLARATION_CLOSE) 

+

25 if end != -1: 25 ↛ exitline 25 didn't return from function 'parse_declaration' because the condition on line 25 was always true

+

26 return body[:end + len(XML_DECLARATION_CLOSE)] 

+

27 

+

28 

+

29def pretty_xml(document: 'Document', 

+

30 declaration: Optional[str] = None, 

+

31 encoding: Optional[str] = UTF8, 

+

32 indent: int = 2) -> str: 

+

33 """Render the given :class:`~xml.dom.minidom.Document` `document` into a prettified string.""" 

+

34 kwargs = { 

+

35 'encoding': encoding or UTF8, 

+

36 'indent': ' ' * indent, 

+

37 } 

+

38 body = document.toprettyxml(**kwargs).decode(kwargs['encoding']) 

+

39 

+

40 # Remove blank lines automatically added by `toprettyxml()`. 

+

41 lines = [line for line in body.splitlines() if line.strip()] 

+

42 

+

43 # xml.dom automatically adds the declaration, even if 

+

44 # it is not present in the actual body. Remove it. 

+

45 if len(lines) >= 1 and parse_declaration(lines[0]): 45 ↛ 50line 45 didn't jump to line 50 because the condition on line 45 was always true

+

46 lines.pop(0) 

+

47 if declaration: 

+

48 lines.insert(0, declaration) 

+

49 

+

50 return '\n'.join(lines) 

+

51 

+

52 

+

53class XMLFormatter(FormatterPlugin): 

+

54 

+

55 def __init__(self, **kwargs): 

+

56 super().__init__(**kwargs) 

+

57 self.enabled = self.format_options['xml']['format'] 

+

58 

+

59 def format_body(self, body: str, mime: str): 

+

60 if 'xml' not in mime: 

+

61 return body 

+

62 

+

63 from xml.parsers.expat import ExpatError 

+

64 from defusedxml.common import DefusedXmlException 

+

65 

+

66 declaration = parse_declaration(body) 

+

67 try: 

+

68 parsed_body = parse_xml(body) 

+

69 except ExpatError: 

+

70 pass # Invalid XML, ignore. 

+

71 except DefusedXmlException: 

+

72 pass # Unsafe XML, ignore. 

+

73 else: 

+

74 body = pretty_xml(parsed_body, 

+

75 encoding=parsed_body.encoding, 

+

76 indent=self.format_options['xml']['indent'], 

+

77 declaration=declaration) 

+

78 

+

79 return body 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___init___py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___init___py.html new file mode 100644 index 0000000000..86675db2d6 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___init___py.html @@ -0,0 +1,108 @@ + + + + + Coverage for httpie\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\__init__.py: + 100% +

+ +

+ 4 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2HTTPie: modern, user-friendly command-line HTTP client for the API era. 

+

3 

+

4""" 

+

5 

+

6__version__ = '3.2.4' 

+

7__date__ = '2024-11-01' 

+

8__author__ = 'Jakub Roztocil' 

+

9__licence__ = 'BSD' 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___main___py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___main___py.html new file mode 100644 index 0000000000..c793e59df5 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29___main___py.html @@ -0,0 +1,118 @@ + + + + + Coverage for httpie\__main__.py: 100% + + + + + +
+
+

+ Coverage for httpie\__main__.py: + 100% +

+ +

+ 8 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1"""The main entry point. Invoke as `http' or `python -m httpie'. 

+

2 

+

3""" 

+

4 

+

5 

+

6def main(): 

+

7 try: 

+

8 from httpie.core import main 

+

9 exit_status = main() 

+

10 except KeyboardInterrupt: 

+

11 from httpie.status import ExitStatus 

+

12 exit_status = ExitStatus.ERROR_CTRL_C 

+

13 

+

14 return exit_status.value 

+

15 

+

16 

+

17if __name__ == '__main__': # pragma: nocover 

+

18 import sys 

+

19 sys.exit(main()) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_adapters_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_adapters_py.html new file mode 100644 index 0000000000..38285fb69f --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_adapters_py.html @@ -0,0 +1,112 @@ + + + + + Coverage for httpie\adapters.py: 100% + + + + + +
+
+

+ Coverage for httpie\adapters.py: + 100% +

+ +

+ 7 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from httpie.cli.dicts import HTTPHeadersDict 

+

2from requests.adapters import HTTPAdapter 

+

3 

+

4 

+

5class HTTPieHTTPAdapter(HTTPAdapter): 

+

6 

+

7 def build_response(self, req, resp): 

+

8 """Wrap the original headers with the `HTTPHeadersDict` 

+

9 to preserve multiple headers that have the same name""" 

+

10 

+

11 response = super().build_response(req, resp) 

+

12 response.headers = HTTPHeadersDict(getattr(resp, 'headers', {})) 

+

13 return response 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_client_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_client_py.html new file mode 100644 index 0000000000..d67bc7c085 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_client_py.html @@ -0,0 +1,499 @@ + + + + + Coverage for httpie\client.py: 99% + + + + + +
+
+

+ Coverage for httpie\client.py: + 99% +

+ +

+ 172 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import http.client 

+

3import json 

+

4import sys 

+

5from contextlib import contextmanager 

+

6from time import monotonic 

+

7from typing import Any, Dict, Callable, Iterable 

+

8from urllib.parse import urlparse, urlunparse 

+

9 

+

10import requests 

+

11# noinspection PyPackageRequirements 

+

12import urllib3 

+

13from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS 

+

14 

+

15from . import __version__ 

+

16from .adapters import HTTPieHTTPAdapter 

+

17from .cli.constants import HTTP_OPTIONS 

+

18from .cli.dicts import HTTPHeadersDict 

+

19from .cli.nested_json import unwrap_top_level_list_if_needed 

+

20from .context import Environment 

+

21from .encoding import UTF8 

+

22from .models import RequestsMessage 

+

23from .plugins.registry import plugin_manager 

+

24from .sessions import get_httpie_session 

+

25from .ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieCertificate, HTTPieHTTPSAdapter 

+

26from .uploads import ( 

+

27 compress_request, prepare_request_body, 

+

28 get_multipart_data_and_content_type, 

+

29) 

+

30from .utils import get_expired_cookies, repr_dict 

+

31 

+

32 

+

33urllib3.disable_warnings() 

+

34 

+

35FORM_CONTENT_TYPE = f'application/x-www-form-urlencoded; charset={UTF8}' 

+

36JSON_CONTENT_TYPE = 'application/json' 

+

37JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5' 

+

38DEFAULT_UA = f'HTTPie/{__version__}' 

+

39 

+

40IGNORE_CONTENT_LENGTH_METHODS = frozenset([HTTP_OPTIONS]) 

+

41 

+

42 

+

43def collect_messages( 

+

44 env: Environment, 

+

45 args: argparse.Namespace, 

+

46 request_body_read_callback: Callable[[bytes], None] = None, 

+

47) -> Iterable[RequestsMessage]: 

+

48 httpie_session = None 

+

49 httpie_session_headers = None 

+

50 if args.session or args.session_read_only: 

+

51 httpie_session = get_httpie_session( 

+

52 env=env, 

+

53 config_dir=env.config.directory, 

+

54 session_name=args.session or args.session_read_only, 

+

55 host=args.headers.get('Host'), 

+

56 url=args.url, 

+

57 ) 

+

58 httpie_session_headers = httpie_session.headers 

+

59 

+

60 request_kwargs = make_request_kwargs( 

+

61 env, 

+

62 args=args, 

+

63 base_headers=httpie_session_headers, 

+

64 request_body_read_callback=request_body_read_callback 

+

65 ) 

+

66 send_kwargs = make_send_kwargs(args) 

+

67 send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args) 

+

68 requests_session = build_requests_session( 

+

69 ssl_version=args.ssl_version, 

+

70 ciphers=args.ciphers, 

+

71 verify=bool(send_kwargs_mergeable_from_env['verify']) 

+

72 ) 

+

73 

+

74 if httpie_session: 

+

75 httpie_session.update_headers(request_kwargs['headers']) 

+

76 requests_session.cookies = httpie_session.cookies 

+

77 if args.auth_plugin: 

+

78 # Save auth from CLI to HTTPie session. 

+

79 httpie_session.auth = { 

+

80 'type': args.auth_plugin.auth_type, 

+

81 'raw_auth': args.auth_plugin.raw_auth, 

+

82 } 

+

83 elif httpie_session.auth: 

+

84 # Apply auth from HTTPie session 

+

85 request_kwargs['auth'] = httpie_session.auth 

+

86 

+

87 if args.debug: 

+

88 # TODO: reflect the split between request and send kwargs. 

+

89 dump_request(request_kwargs) 

+

90 

+

91 request = requests.Request(**request_kwargs) 

+

92 prepared_request = requests_session.prepare_request(request) 

+

93 transform_headers(request, prepared_request) 

+

94 if args.path_as_is: 

+

95 prepared_request.url = ensure_path_as_is( 

+

96 orig_url=args.url, 

+

97 prepped_url=prepared_request.url, 

+

98 ) 

+

99 if args.compress and prepared_request.body: 

+

100 compress_request( 

+

101 request=prepared_request, 

+

102 always=args.compress > 1, 

+

103 ) 

+

104 response_count = 0 

+

105 expired_cookies = [] 

+

106 while prepared_request: 106 ↛ 136line 106 didn't jump to line 136 because the condition on line 106 was always true

+

107 yield prepared_request 

+

108 if not args.offline: 

+

109 send_kwargs_merged = requests_session.merge_environment_settings( 

+

110 url=prepared_request.url, 

+

111 **send_kwargs_mergeable_from_env, 

+

112 ) 

+

113 with max_headers(args.max_headers): 

+

114 response = requests_session.send( 

+

115 request=prepared_request, 

+

116 **send_kwargs_merged, 

+

117 **send_kwargs, 

+

118 ) 

+

119 response._httpie_headers_parsed_at = monotonic() 

+

120 expired_cookies += get_expired_cookies( 

+

121 response.headers.get('Set-Cookie', '') 

+

122 ) 

+

123 

+

124 response_count += 1 

+

125 if response.next: 

+

126 if args.max_redirects and response_count == args.max_redirects: 

+

127 raise requests.TooManyRedirects 

+

128 if args.follow: 

+

129 prepared_request = response.next 

+

130 if args.all: 

+

131 yield response 

+

132 continue 

+

133 yield response 

+

134 break 

+

135 

+

136 if httpie_session: 

+

137 if httpie_session.is_new() or not args.session_read_only: 

+

138 httpie_session.cookies = requests_session.cookies 

+

139 httpie_session.remove_cookies(expired_cookies) 

+

140 httpie_session.save() 

+

141 

+

142 

+

143# noinspection PyProtectedMember 

+

144@contextmanager 

+

145def max_headers(limit): 

+

146 # <https://github.com/httpie/cli/issues/802> 

+

147 # noinspection PyUnresolvedReferences 

+

148 orig = http.client._MAXHEADERS 

+

149 http.client._MAXHEADERS = limit or float('Inf') 

+

150 try: 

+

151 yield 

+

152 finally: 

+

153 http.client._MAXHEADERS = orig 

+

154 

+

155 

+

156def build_requests_session( 

+

157 verify: bool, 

+

158 ssl_version: str = None, 

+

159 ciphers: str = None, 

+

160) -> requests.Session: 

+

161 requests_session = requests.Session() 

+

162 

+

163 # Install our adapter. 

+

164 http_adapter = HTTPieHTTPAdapter() 

+

165 https_adapter = HTTPieHTTPSAdapter( 

+

166 ciphers=ciphers, 

+

167 verify=verify, 

+

168 ssl_version=( 

+

169 AVAILABLE_SSL_VERSION_ARG_MAPPING[ssl_version] 

+

170 if ssl_version else None 

+

171 ), 

+

172 ) 

+

173 requests_session.mount('http://', http_adapter) 

+

174 requests_session.mount('https://', https_adapter) 

+

175 

+

176 # Install adapters from plugins. 

+

177 for plugin_cls in plugin_manager.get_transport_plugins(): 

+

178 transport_plugin = plugin_cls() 

+

179 requests_session.mount( 

+

180 prefix=transport_plugin.prefix, 

+

181 adapter=transport_plugin.get_adapter(), 

+

182 ) 

+

183 

+

184 return requests_session 

+

185 

+

186 

+

187def dump_request(kwargs: dict): 

+

188 sys.stderr.write( 

+

189 f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n') 

+

190 

+

191 

+

192def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict: 

+

193 final_headers = HTTPHeadersDict() 

+

194 for name, value in headers.items(): 

+

195 if value is not None: 

+

196 # “leading or trailing LWS MAY be removed without 

+

197 # changing the semantics of the field value” 

+

198 # <https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html> 

+

199 # Also, requests raises `InvalidHeader` for leading spaces. 

+

200 value = value.strip() 

+

201 if isinstance(value, str): 201 ↛ 208line 201 didn't jump to line 208 because the condition on line 201 was always true

+

202 # See <https://github.com/httpie/cli/issues/212> 

+

203 value = value.encode() 

+

204 elif name.lower() in SKIPPABLE_HEADERS: 

+

205 # Some headers get overwritten by urllib3 when set to `None` 

+

206 # and should be replaced with the `SKIP_HEADER` constant. 

+

207 value = SKIP_HEADER 

+

208 final_headers.add(name, value) 

+

209 return final_headers 

+

210 

+

211 

+

212def transform_headers( 

+

213 request: requests.Request, 

+

214 prepared_request: requests.PreparedRequest 

+

215) -> None: 

+

216 """Apply various transformations on top of the `prepared_requests`'s 

+

217 headers to change the request prepreation behavior.""" 

+

218 

+

219 # Remove 'Content-Length' when it is misplaced by requests. 

+

220 if ( 

+

221 prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS 

+

222 and prepared_request.headers.get('Content-Length') == '0' 

+

223 and request.headers.get('Content-Length') != '0' 

+

224 ): 

+

225 prepared_request.headers.pop('Content-Length') 

+

226 

+

227 apply_missing_repeated_headers( 

+

228 request.headers, 

+

229 prepared_request 

+

230 ) 

+

231 

+

232 

+

233def apply_missing_repeated_headers( 

+

234 original_headers: HTTPHeadersDict, 

+

235 prepared_request: requests.PreparedRequest 

+

236) -> None: 

+

237 """Update the given `prepared_request`'s headers with the original 

+

238 ones. This allows the requests to be prepared as usual, and then later 

+

239 merged with headers that are specified multiple times.""" 

+

240 

+

241 new_headers = HTTPHeadersDict(prepared_request.headers) 

+

242 for prepared_name, prepared_value in prepared_request.headers.items(): 

+

243 if prepared_name not in original_headers: 

+

244 continue 

+

245 

+

246 original_keys, original_values = zip(*filter( 

+

247 lambda item: item[0].casefold() == prepared_name.casefold(), 

+

248 original_headers.items() 

+

249 )) 

+

250 

+

251 if prepared_value not in original_values: 

+

252 # If the current value is not among the initial values 

+

253 # set for this field, then it means that this field got 

+

254 # overridden on the way, and we should preserve it. 

+

255 continue 

+

256 

+

257 new_headers.popone(prepared_name) 

+

258 new_headers.update(zip(original_keys, original_values)) 

+

259 

+

260 prepared_request.headers = new_headers 

+

261 

+

262 

+

263def make_default_headers(args: argparse.Namespace) -> HTTPHeadersDict: 

+

264 default_headers = HTTPHeadersDict({ 

+

265 'User-Agent': DEFAULT_UA 

+

266 }) 

+

267 

+

268 auto_json = args.data and not args.form 

+

269 if args.json or auto_json: 

+

270 default_headers['Accept'] = JSON_ACCEPT 

+

271 if args.json or (auto_json and args.data): 271 ↛ 278line 271 didn't jump to line 278 because the condition on line 271 was always true

+

272 default_headers['Content-Type'] = JSON_CONTENT_TYPE 

+

273 

+

274 elif args.form and not args.files: 

+

275 # If sending files, `requests` will set 

+

276 # the `Content-Type` for us. 

+

277 default_headers['Content-Type'] = FORM_CONTENT_TYPE 

+

278 return default_headers 

+

279 

+

280 

+

281def make_send_kwargs(args: argparse.Namespace) -> dict: 

+

282 return { 

+

283 'timeout': args.timeout or None, 

+

284 'allow_redirects': False, 

+

285 } 

+

286 

+

287 

+

288def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict: 

+

289 cert = None 

+

290 if args.cert: 

+

291 cert = args.cert 

+

292 if args.cert_key: 

+

293 # Having a client certificate key passphrase is not supported 

+

294 # by requests. So we are using our own transportation structure 

+

295 # which is compatible with their format (a tuple of minimum two 

+

296 # items). 

+

297 # 

+

298 # See: https://github.com/psf/requests/issues/2519 

+

299 cert = HTTPieCertificate(cert, args.cert_key, args.cert_key_pass.value) 

+

300 

+

301 return { 

+

302 'proxies': {p.key: p.value for p in args.proxy}, 

+

303 'stream': True, 

+

304 'verify': { 

+

305 'yes': True, 

+

306 'true': True, 

+

307 'no': False, 

+

308 'false': False, 

+

309 }.get(args.verify.lower(), args.verify), 

+

310 'cert': cert, 

+

311 } 

+

312 

+

313 

+

314def json_dict_to_request_body(data: Dict[str, Any]) -> str: 

+

315 data = unwrap_top_level_list_if_needed(data) 

+

316 if data: 

+

317 data = json.dumps(data) 

+

318 else: 

+

319 # We need to set data to an empty string to prevent requests 

+

320 # from assigning an empty list to `response.request.data`. 

+

321 data = '' 

+

322 return data 

+

323 

+

324 

+

325def make_request_kwargs( 

+

326 env: Environment, 

+

327 args: argparse.Namespace, 

+

328 base_headers: HTTPHeadersDict = None, 

+

329 request_body_read_callback=lambda chunk: chunk 

+

330) -> dict: 

+

331 """ 

+

332 Translate our `args` into `requests.Request` keyword arguments. 

+

333 

+

334 """ 

+

335 files = args.files 

+

336 # Serialize JSON data, if needed. 

+

337 data = args.data 

+

338 auto_json = data and not args.form 

+

339 if (args.json or auto_json) and isinstance(data, dict): 

+

340 data = json_dict_to_request_body(data) 

+

341 

+

342 # Finalize headers. 

+

343 headers = make_default_headers(args) 

+

344 if base_headers: 

+

345 headers.update(base_headers) 

+

346 headers.update(args.headers) 

+

347 if args.offline and args.chunked and 'Transfer-Encoding' not in headers: 

+

348 # When online, we let requests set the header instead to be able more 

+

349 # easily verify chunking is taking place. 

+

350 headers['Transfer-Encoding'] = 'chunked' 

+

351 headers = finalize_headers(headers) 

+

352 

+

353 if (args.form and files) or args.multipart: 

+

354 data, headers['Content-Type'] = get_multipart_data_and_content_type( 

+

355 data=args.multipart_data, 

+

356 boundary=args.boundary, 

+

357 content_type=args.headers.get('Content-Type'), 

+

358 ) 

+

359 

+

360 return { 

+

361 'method': args.method.lower(), 

+

362 'url': args.url, 

+

363 'headers': headers, 

+

364 'data': prepare_request_body( 

+

365 env, 

+

366 data, 

+

367 body_read_callback=request_body_read_callback, 

+

368 chunked=args.chunked, 

+

369 offline=args.offline, 

+

370 content_length_header_value=headers.get('Content-Length'), 

+

371 ), 

+

372 'auth': args.auth, 

+

373 'params': args.params.items(), 

+

374 } 

+

375 

+

376 

+

377def ensure_path_as_is(orig_url: str, prepped_url: str) -> str: 

+

378 """ 

+

379 Handle `--path-as-is` by replacing the path component of the prepared 

+

380 URL with the path component from the original URL. Other parts stay 

+

381 untouched because other (welcome) processing on the URL might have 

+

382 taken place. 

+

383 

+

384 <https://github.com/httpie/cli/issues/895> 

+

385 

+

386 

+

387 <https://ec.haxx.se/http/http-basics#path-as-is> 

+

388 <https://curl.haxx.se/libcurl/c/CURLOPT_PATH_AS_IS.html> 

+

389 

+

390 >>> ensure_path_as_is('http://foo/../', 'http://foo/?foo=bar') 

+

391 'http://foo/../?foo=bar' 

+

392 

+

393 """ 

+

394 parsed_orig, parsed_prepped = urlparse(orig_url), urlparse(prepped_url) 

+

395 final_dict = { 

+

396 # noinspection PyProtectedMember 

+

397 **parsed_prepped._asdict(), 

+

398 'path': parsed_orig.path, 

+

399 } 

+

400 return urlunparse(tuple(final_dict.values())) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_compat_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_compat_py.html new file mode 100644 index 0000000000..1c114181b5 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_compat_py.html @@ -0,0 +1,212 @@ + + + + + Coverage for httpie\compat.py: 41% + + + + + +
+
+

+ Coverage for httpie\compat.py: + 41% +

+ +

+ 56 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import sys 

+

2from ssl import SSLContext 

+

3from typing import Any, Optional, Iterable 

+

4 

+

5from httpie.cookies import HTTPieCookiePolicy 

+

6from http import cookiejar # noqa 

+

7 

+

8 

+

9# Request does not carry the original policy attached to the 

+

10# cookie jar, so until it is resolved we change the global cookie 

+

11# policy. <https://github.com/psf/requests/issues/5449> 

+

12cookiejar.DefaultCookiePolicy = HTTPieCookiePolicy 

+

13 

+

14is_windows = 'win32' in str(sys.platform).lower() 

+

15is_frozen = getattr(sys, 'frozen', False) 

+

16 

+

17MIN_SUPPORTED_PY_VERSION = (3, 7) 

+

18MAX_SUPPORTED_PY_VERSION = (3, 11) 

+

19 

+

20try: 

+

21 from functools import cached_property 

+

22except ImportError: 

+

23 # Can be removed once we drop Python <3.8 support. 

+

24 # Taken from `django.utils.functional.cached_property`. 

+

25 class cached_property: 

+

26 """ 

+

27 Decorator that converts a method with a single self argument into a 

+

28 property cached on the instance. 

+

29 

+

30 A cached property can be made out of an existing method: 

+

31 (e.g. ``url = cached_property(get_absolute_url)``). 

+

32 The optional ``name`` argument is obsolete as of Python 3.6 and will be 

+

33 deprecated in Django 4.0 (#30127). 

+

34 """ 

+

35 name = None 

+

36 

+

37 @staticmethod 

+

38 def func(instance): 

+

39 raise TypeError( 

+

40 'Cannot use cached_property instance without calling ' 

+

41 '__set_name__() on it.' 

+

42 ) 

+

43 

+

44 def __init__(self, func, name=None): 

+

45 self.real_func = func 

+

46 self.__doc__ = getattr(func, '__doc__') 

+

47 

+

48 def __set_name__(self, owner, name): 

+

49 if self.name is None: 

+

50 self.name = name 

+

51 self.func = self.real_func 

+

52 elif name != self.name: 

+

53 raise TypeError( 

+

54 "Cannot assign the same cached_property to two different names " 

+

55 "(%r and %r)." % (self.name, name) 

+

56 ) 

+

57 

+

58 def __get__(self, instance, cls=None): 

+

59 """ 

+

60 Call the function and put the return value in instance.__dict__ so that 

+

61 subsequent attribute access on the instance returns the cached value 

+

62 instead of calling cached_property.__get__(). 

+

63 """ 

+

64 if instance is None: 

+

65 return self 

+

66 res = instance.__dict__[self.name] = self.func(instance) 

+

67 return res 

+

68 

+

69# importlib_metadata was a provisional module, so the APIs changed quite a few times 

+

70# between 3.8-3.10. It was also not included in the standard library until 3.8, so 

+

71# we install the backport for <3.8. 

+

72 

+

73if sys.version_info >= (3, 8): 73 ↛ 76line 73 didn't jump to line 76 because the condition on line 73 was always true

+

74 import importlib.metadata as importlib_metadata 

+

75else: 

+

76 import importlib_metadata 

+

77 

+

78 

+

79def find_entry_points(entry_points: Any, group: str) -> Iterable[importlib_metadata.EntryPoint]: 

+

80 if hasattr(entry_points, "select"): # Python 3.10+ / importlib_metadata >= 3.9.0 80 ↛ 83line 80 didn't jump to line 83 because the condition on line 80 was always true

+

81 return entry_points.select(group=group) 

+

82 else: 

+

83 return set(entry_points.get(group, ())) 

+

84 

+

85 

+

86def get_dist_name(entry_point: importlib_metadata.EntryPoint) -> Optional[str]: 

+

87 dist = getattr(entry_point, "dist", None) 

+

88 if dist is not None: # Python 3.10+ 88 ↛ 91line 88 didn't jump to line 91 because the condition on line 88 was always true

+

89 return dist.name 

+

90 

+

91 match = entry_point.pattern.match(entry_point.value) 

+

92 if not (match and match.group('module')): 

+

93 return None 

+

94 

+

95 package = match.group('module').split('.')[0] 

+

96 try: 

+

97 metadata = importlib_metadata.metadata(package) 

+

98 except importlib_metadata.PackageNotFoundError: 

+

99 return None 

+

100 else: 

+

101 return metadata.get('name') 

+

102 

+

103 

+

104def ensure_default_certs_loaded(ssl_context: SSLContext) -> None: 

+

105 """ 

+

106 Workaround for a bug in Requests 2.32.3 

+

107 

+

108 See <https://github.com/httpie/cli/issues/1583> 

+

109 

+

110 """ 

+

111 if hasattr(ssl_context, 'load_default_certs'): 111 ↛ exitline 111 didn't return from function 'ensure_default_certs_loaded' because the condition on line 111 was always true

+

112 if not ssl_context.get_ca_certs(): 112 ↛ exitline 112 didn't return from function 'ensure_default_certs_loaded' because the condition on line 112 was always true

+

113 ssl_context.load_default_certs() 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_config_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_config_py.html new file mode 100644 index 0000000000..a141737cc6 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_config_py.html @@ -0,0 +1,271 @@ + + + + + Coverage for httpie\config.py: 90% + + + + + +
+
+

+ Coverage for httpie\config.py: + 90% +

+ +

+ 95 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import json 

+

2import os 

+

3from pathlib import Path 

+

4from typing import Any, Dict, Union 

+

5 

+

6from . import __version__ 

+

7from .compat import is_windows 

+

8from .encoding import UTF8 

+

9 

+

10 

+

11ENV_XDG_CONFIG_HOME = 'XDG_CONFIG_HOME' 

+

12ENV_HTTPIE_CONFIG_DIR = 'HTTPIE_CONFIG_DIR' 

+

13DEFAULT_CONFIG_DIRNAME = 'httpie' 

+

14DEFAULT_RELATIVE_XDG_CONFIG_HOME = Path('.config') 

+

15DEFAULT_RELATIVE_LEGACY_CONFIG_DIR = Path('.httpie') 

+

16DEFAULT_WINDOWS_CONFIG_DIR = Path( 

+

17 os.path.expandvars('%APPDATA%')) / DEFAULT_CONFIG_DIRNAME 

+

18 

+

19 

+

20def get_default_config_dir() -> Path: 

+

21 """ 

+

22 Return the path to the httpie configuration directory. 

+

23 

+

24 This directory isn't guaranteed to exist, and nor are any of its 

+

25 ancestors (only the legacy ~/.httpie, if returned, is guaranteed to exist). 

+

26 

+

27 XDG Base Directory Specification support: 

+

28 

+

29 <https://wiki.archlinux.org/index.php/XDG_Base_Directory> 

+

30 

+

31 $XDG_CONFIG_HOME is supported; $XDG_CONFIG_DIRS is not 

+

32 

+

33 """ 

+

34 # 1. explicitly set through env 

+

35 env_config_dir = os.environ.get(ENV_HTTPIE_CONFIG_DIR) 

+

36 if env_config_dir: 

+

37 return Path(env_config_dir) 

+

38 

+

39 # 2. Windows 

+

40 if is_windows: 40 ↛ 43line 40 didn't jump to line 43 because the condition on line 40 was always true

+

41 return DEFAULT_WINDOWS_CONFIG_DIR 

+

42 

+

43 home_dir = Path.home() 

+

44 

+

45 # 3. legacy ~/.httpie 

+

46 legacy_config_dir = home_dir / DEFAULT_RELATIVE_LEGACY_CONFIG_DIR 

+

47 if legacy_config_dir.exists(): 

+

48 return legacy_config_dir 

+

49 

+

50 # 4. XDG 

+

51 xdg_config_home_dir = os.environ.get( 

+

52 ENV_XDG_CONFIG_HOME, # 4.1. explicit 

+

53 home_dir / DEFAULT_RELATIVE_XDG_CONFIG_HOME # 4.2. default 

+

54 ) 

+

55 return Path(xdg_config_home_dir) / DEFAULT_CONFIG_DIRNAME 

+

56 

+

57 

+

58DEFAULT_CONFIG_DIR = get_default_config_dir() 

+

59 

+

60 

+

61class ConfigFileError(Exception): 

+

62 pass 

+

63 

+

64 

+

65def read_raw_config(config_type: str, path: Path) -> Dict[str, Any]: 

+

66 try: 

+

67 with path.open(encoding=UTF8) as f: 

+

68 try: 

+

69 return json.load(f) 

+

70 except ValueError as e: 

+

71 raise ConfigFileError( 

+

72 f'invalid {config_type} file: {e} [{path}]' 

+

73 ) 

+

74 except FileNotFoundError: 

+

75 pass 

+

76 except OSError as e: 

+

77 raise ConfigFileError(f'cannot read {config_type} file: {e}') 

+

78 

+

79 

+

80class BaseConfigDict(dict): 

+

81 name = None 

+

82 helpurl = None 

+

83 about = None 

+

84 

+

85 def __init__(self, path: Path): 

+

86 super().__init__() 

+

87 self.path = path 

+

88 

+

89 def ensure_directory(self): 

+

90 self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True) 

+

91 

+

92 def is_new(self) -> bool: 

+

93 return not self.path.exists() 

+

94 

+

95 def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]: 

+

96 """Hook for processing the incoming config data.""" 

+

97 return data 

+

98 

+

99 def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]: 

+

100 """Hook for processing the outgoing config data.""" 

+

101 return data 

+

102 

+

103 def load(self): 

+

104 config_type = type(self).__name__.lower() 

+

105 data = read_raw_config(config_type, self.path) 

+

106 if data is not None: 

+

107 data = self.pre_process_data(data) 

+

108 self.update(data) 

+

109 

+

110 def save(self, *, bump_version: bool = False): 

+

111 self.setdefault('__meta__', {}) 

+

112 if bump_version or 'httpie' not in self['__meta__']: 

+

113 self['__meta__']['httpie'] = __version__ 

+

114 if self.helpurl: 

+

115 self['__meta__']['help'] = self.helpurl 

+

116 

+

117 if self.about: 

+

118 self['__meta__']['about'] = self.about 

+

119 

+

120 self.ensure_directory() 

+

121 

+

122 json_string = json.dumps( 

+

123 obj=self.post_process_data(self), 

+

124 indent=4, 

+

125 sort_keys=True, 

+

126 ensure_ascii=True, 

+

127 ) 

+

128 self.path.write_text(json_string + '\n', encoding=UTF8) 

+

129 

+

130 @property 

+

131 def version(self): 

+

132 return self.get( 

+

133 '__meta__', {} 

+

134 ).get('httpie', __version__) 

+

135 

+

136 

+

137class Config(BaseConfigDict): 

+

138 FILENAME = 'config.json' 

+

139 DEFAULTS = { 

+

140 'default_options': [] 

+

141 } 

+

142 

+

143 def __init__(self, directory: Union[str, Path] = DEFAULT_CONFIG_DIR): 

+

144 self.directory = Path(directory) 

+

145 super().__init__(path=self.directory / self.FILENAME) 

+

146 self.update(self.DEFAULTS) 

+

147 

+

148 @property 

+

149 def default_options(self) -> list: 

+

150 return self['default_options'] 

+

151 

+

152 def _configured_path(self, config_option: str, default: str) -> None: 

+

153 return Path( 

+

154 self.get(config_option, self.directory / default) 

+

155 ).expanduser().resolve() 

+

156 

+

157 @property 

+

158 def plugins_dir(self) -> Path: 

+

159 return self._configured_path('plugins_dir', 'plugins') 

+

160 

+

161 @property 

+

162 def version_info_file(self) -> Path: 

+

163 return self._configured_path('version_info_file', 'version_info.json') 

+

164 

+

165 @property 

+

166 def developer_mode(self) -> bool: 

+

167 """This is a special setting for the development environment. It is 

+

168 different from the --debug mode in the terms that it might change 

+

169 the behavior for certain parameters (e.g updater system) that 

+

170 we usually ignore.""" 

+

171 

+

172 return self.get('developer_mode') 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_context_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_context_py.html new file mode 100644 index 0000000000..5d0bcd920d --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_context_py.html @@ -0,0 +1,316 @@ + + + + + Coverage for httpie\context.py: 89% + + + + + +
+
+

+ Coverage for httpie\context.py: + 89% +

+ +

+ 120 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import sys 

+

3import os 

+

4import warnings 

+

5from contextlib import contextmanager 

+

6from pathlib import Path 

+

7from typing import Iterator, IO, Optional, TYPE_CHECKING 

+

8from enum import Enum 

+

9 

+

10 

+

11try: 

+

12 import curses 

+

13except ImportError: 

+

14 curses = None # Compiled w/o curses 

+

15 

+

16from .compat import is_windows, cached_property 

+

17from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError 

+

18from .encoding import UTF8 

+

19 

+

20from .utils import repr_dict 

+

21from .output.ui.palette import GenericColor 

+

22 

+

23if TYPE_CHECKING: 

+

24 from rich.console import Console 

+

25 

+

26 

+

27class LogLevel(str, Enum): 

+

28 INFO = 'info' 

+

29 WARNING = 'warning' 

+

30 ERROR = 'error' 

+

31 

+

32 

+

33LOG_LEVEL_COLORS = { 

+

34 LogLevel.INFO: GenericColor.PINK, 

+

35 LogLevel.WARNING: GenericColor.ORANGE, 

+

36 LogLevel.ERROR: GenericColor.RED, 

+

37} 

+

38 

+

39LOG_LEVEL_DISPLAY_THRESHOLDS = { 

+

40 LogLevel.INFO: 1, 

+

41 LogLevel.WARNING: 2, 

+

42 LogLevel.ERROR: float('inf'), # Never hide errors. 

+

43} 

+

44 

+

45 

+

46class Environment: 

+

47 """ 

+

48 Information about the execution context 

+

49 (standard streams, config directory, etc). 

+

50 

+

51 By default, it represents the actual environment. 

+

52 All of the attributes can be overwritten though, which 

+

53 is used by the test suite to simulate various scenarios. 

+

54 

+

55 """ 

+

56 args = argparse.Namespace() 

+

57 is_windows: bool = is_windows 

+

58 config_dir: Path = DEFAULT_CONFIG_DIR 

+

59 stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791) 

+

60 stdin_isatty: bool = stdin.isatty() if stdin else False 

+

61 stdin_encoding: str = None 

+

62 stdout: IO = sys.stdout 

+

63 stdout_isatty: bool = stdout.isatty() 

+

64 stdout_encoding: str = None 

+

65 stderr: IO = sys.stderr 

+

66 stderr_isatty: bool = stderr.isatty() 

+

67 colors = 256 

+

68 program_name: str = 'http' 

+

69 

+

70 # Whether to show progress bars / status spinners etc. 

+

71 show_displays: bool = True 

+

72 

+

73 if not is_windows: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true

+

74 if curses: 

+

75 try: 

+

76 curses.setupterm() 

+

77 colors = curses.tigetnum('colors') 

+

78 except curses.error: 

+

79 pass 

+

80 else: 

+

81 # noinspection PyUnresolvedReferences 

+

82 import colorama.initialise 

+

83 stdout = colorama.initialise.wrap_stream( 

+

84 stdout, convert=None, strip=None, 

+

85 autoreset=True, wrap=True 

+

86 ) 

+

87 stderr = colorama.initialise.wrap_stream( 

+

88 stderr, convert=None, strip=None, 

+

89 autoreset=True, wrap=True 

+

90 ) 

+

91 del colorama 

+

92 

+

93 def __init__(self, devnull=None, **kwargs): 

+

94 """ 

+

95 Use keyword arguments to overwrite 

+

96 any of the class attributes for this instance. 

+

97 

+

98 """ 

+

99 assert all(hasattr(type(self), attr) for attr in kwargs.keys()) 

+

100 self.__dict__.update(**kwargs) 

+

101 

+

102 # The original STDERR unaffected by --quiet’ing. 

+

103 self._orig_stderr = self.stderr 

+

104 self._devnull = devnull 

+

105 

+

106 # Keyword arguments > stream.encoding > default UTF-8 

+

107 if self.stdin and self.stdin_encoding is None: 

+

108 self.stdin_encoding = getattr( 

+

109 self.stdin, 'encoding', None) or UTF8 

+

110 if self.stdout_encoding is None: 110 ↛ 121line 110 didn't jump to line 121 because the condition on line 110 was always true

+

111 actual_stdout = self.stdout 

+

112 if is_windows: 112 ↛ 118line 112 didn't jump to line 118 because the condition on line 112 was always true

+

113 # noinspection PyUnresolvedReferences 

+

114 from colorama import AnsiToWin32 

+

115 if isinstance(self.stdout, AnsiToWin32): 115 ↛ 117line 115 didn't jump to line 117 because the condition on line 115 was never true

+

116 # noinspection PyUnresolvedReferences 

+

117 actual_stdout = self.stdout.wrapped 

+

118 self.stdout_encoding = getattr( 

+

119 actual_stdout, 'encoding', None) or UTF8 

+

120 

+

121 self.quiet = kwargs.pop('quiet', 0) 

+

122 

+

123 def __str__(self): 

+

124 defaults = dict(type(self).__dict__) 

+

125 actual = dict(defaults) 

+

126 actual.update(self.__dict__) 

+

127 actual['config'] = self.config 

+

128 return repr_dict({ 

+

129 key: value 

+

130 for key, value in actual.items() 

+

131 if not key.startswith('_') 

+

132 }) 

+

133 

+

134 def __repr__(self): 

+

135 return f'<{type(self).__name__} {self}>' 

+

136 

+

137 _config: Config = None 

+

138 

+

139 @property 

+

140 def config(self) -> Config: 

+

141 config = self._config 

+

142 if not config: 

+

143 self._config = config = Config(directory=self.config_dir) 

+

144 if not config.is_new(): 

+

145 try: 

+

146 config.load() 

+

147 except ConfigFileError as e: 

+

148 self.log_error(e, level=LogLevel.WARNING) 

+

149 return config 

+

150 

+

151 @property 

+

152 def devnull(self) -> IO: 

+

153 if self._devnull is None: 

+

154 self._devnull = open(os.devnull, 'w+') 

+

155 return self._devnull 

+

156 

+

157 @contextmanager 

+

158 def as_silent(self) -> Iterator[None]: 

+

159 original_stdout = self.stdout 

+

160 original_stderr = self.stderr 

+

161 

+

162 try: 

+

163 self.stdout = self.devnull 

+

164 self.stderr = self.devnull 

+

165 yield 

+

166 finally: 

+

167 self.stdout = original_stdout 

+

168 self.stderr = original_stderr 

+

169 

+

170 def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None: 

+

171 if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]: 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true

+

172 stderr = self.stderr # Not directly /dev/null, since stderr might be mocked 

+

173 else: 

+

174 stderr = self._orig_stderr 

+

175 rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty()) 

+

176 rich_console.print( 

+

177 f'\n{self.program_name}: {level.value}: {msg}\n\n', 

+

178 style=LOG_LEVEL_COLORS[level], 

+

179 markup=False, 

+

180 highlight=False, 

+

181 soft_wrap=True 

+

182 ) 

+

183 

+

184 def apply_warnings_filter(self) -> None: 

+

185 if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]: 

+

186 warnings.simplefilter("ignore") 

+

187 

+

188 def _make_rich_console( 

+

189 self, 

+

190 file: IO[str], 

+

191 force_terminal: bool 

+

192 ) -> 'Console': 

+

193 from rich.console import Console 

+

194 from httpie.output.ui.rich_palette import _make_rich_color_theme 

+

195 

+

196 style = getattr(self.args, 'style', None) 

+

197 theme = _make_rich_color_theme(style) 

+

198 # Rich infers the rest of the knowledge (e.g encoding) 

+

199 # dynamically by looking at the file/stderr. 

+

200 return Console( 

+

201 file=file, 

+

202 force_terminal=force_terminal, 

+

203 no_color=(self.colors == 0), 

+

204 theme=theme 

+

205 ) 

+

206 

+

207 # Rich recommends separating the actual console (stdout) from 

+

208 # the error (stderr) console for better isolation between parts. 

+

209 # https://rich.readthedocs.io/en/stable/console.html#error-console 

+

210 

+

211 @cached_property 

+

212 def rich_console(self): 

+

213 return self._make_rich_console(self.stdout, self.stdout_isatty) 

+

214 

+

215 @cached_property 

+

216 def rich_error_console(self): 

+

217 return self._make_rich_console(self.stderr, self.stderr_isatty) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_cookies_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_cookies_py.html new file mode 100644 index 0000000000..81b5865483 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_cookies_py.html @@ -0,0 +1,124 @@ + + + + + Coverage for httpie\cookies.py: 100% + + + + + +
+
+

+ Coverage for httpie\cookies.py: + 100% +

+ +

+ 11 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from http import cookiejar 

+

2 

+

3 

+

4_LOCALHOST = 'localhost' 

+

5_LOCALHOST_SUFFIX = '.localhost' 

+

6 

+

7 

+

8class HTTPieCookiePolicy(cookiejar.DefaultCookiePolicy): 

+

9 def return_ok_secure(self, cookie, request): 

+

10 """Check whether the given cookie is sent to a secure host.""" 

+

11 

+

12 is_secure_protocol = super().return_ok_secure(cookie, request) 

+

13 if is_secure_protocol: 

+

14 return True 

+

15 

+

16 # The original implementation of this method only takes secure protocols 

+

17 # (e.g., https) into account, but the latest developments in modern browsers 

+

18 # (chrome, firefox) assume 'localhost' is also a secure location. So we 

+

19 # override it with our own strategy. 

+

20 return self._is_local_host(cookiejar.request_host(request)) 

+

21 

+

22 def _is_local_host(self, hostname): 

+

23 # Implements the static localhost detection algorithm in firefox. 

+

24 # <https://searchfox.org/mozilla-central/rev/d4d7611ee4dd0003b492b865bc5988a4e6afc985/netwerk/dns/DNS.cpp#205-218> 

+

25 return hostname == _LOCALHOST or hostname.endswith(_LOCALHOST_SUFFIX) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_core_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_core_py.html new file mode 100644 index 0000000000..fefab60161 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_core_py.html @@ -0,0 +1,397 @@ + + + + + Coverage for httpie\core.py: 93% + + + + + +
+
+

+ Coverage for httpie\core.py: + 93% +

+ +

+ 170 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import os 

+

3import platform 

+

4import sys 

+

5import socket 

+

6from typing import List, Optional, Union, Callable 

+

7 

+

8import requests 

+

9from pygments import __version__ as pygments_version 

+

10from requests import __version__ as requests_version 

+

11 

+

12from . import __version__ as httpie_version 

+

13from .cli.constants import OUT_REQ_BODY 

+

14from .cli.nested_json import NestedJSONSyntaxError 

+

15from .client import collect_messages 

+

16from .context import Environment, LogLevel 

+

17from .downloads import Downloader 

+

18from .models import ( 

+

19 RequestsMessageKind, 

+

20 OutputOptions 

+

21) 

+

22from .output.models import ProcessingOptions 

+

23from .output.writer import write_message, write_stream, write_raw_data, MESSAGE_SEPARATOR_BYTES 

+

24from .plugins.registry import plugin_manager 

+

25from .status import ExitStatus, http_status_to_exit_status 

+

26from .utils import unwrap_context 

+

27from .internal.update_warnings import check_updates 

+

28from .internal.daemon_runner import is_daemon_mode, run_daemon_task 

+

29 

+

30 

+

31# noinspection PyDefaultArgument 

+

32def raw_main( 

+

33 parser: argparse.ArgumentParser, 

+

34 main_program: Callable[[argparse.Namespace, Environment], ExitStatus], 

+

35 args: List[Union[str, bytes]] = sys.argv, 

+

36 env: Environment = Environment(), 

+

37 use_default_options: bool = True, 

+

38) -> ExitStatus: 

+

39 program_name, *args = args 

+

40 env.program_name = os.path.basename(program_name) 

+

41 args = decode_raw_args(args, env.stdin_encoding) 

+

42 

+

43 if is_daemon_mode(args): 

+

44 return run_daemon_task(env, args) 

+

45 

+

46 plugin_manager.load_installed_plugins(env.config.plugins_dir) 

+

47 

+

48 if use_default_options and env.config.default_options: 

+

49 args = env.config.default_options + args 

+

50 

+

51 include_debug_info = '--debug' in args 

+

52 include_traceback = include_debug_info or '--traceback' in args 

+

53 

+

54 def handle_generic_error(e, annotation=None): 

+

55 msg = str(e) 

+

56 if hasattr(e, 'request'): 

+

57 request = e.request 

+

58 if hasattr(request, 'url'): 

+

59 msg = ( 

+

60 f'{msg} while doing a {request.method}' 

+

61 f' request to URL: {request.url}' 

+

62 ) 

+

63 if annotation: 

+

64 msg += annotation 

+

65 env.log_error(f'{type(e).__name__}: {msg}') 

+

66 if include_traceback: 

+

67 raise 

+

68 

+

69 if include_debug_info: 

+

70 print_debug_info(env) 

+

71 if args == ['--debug']: 

+

72 return ExitStatus.SUCCESS 

+

73 

+

74 exit_status = ExitStatus.SUCCESS 

+

75 

+

76 try: 

+

77 parsed_args = parser.parse_args( 

+

78 args=args, 

+

79 env=env, 

+

80 ) 

+

81 except NestedJSONSyntaxError as exc: 

+

82 env.stderr.write(str(exc) + "\n") 

+

83 if include_traceback: 83 ↛ 85line 83 didn't jump to line 85 because the condition on line 83 was always true

+

84 raise 

+

85 exit_status = ExitStatus.ERROR 

+

86 except KeyboardInterrupt: 

+

87 env.stderr.write('\n') 

+

88 if include_traceback: 88 ↛ 89line 88 didn't jump to line 89 because the condition on line 88 was never true

+

89 raise 

+

90 exit_status = ExitStatus.ERROR_CTRL_C 

+

91 except SystemExit as e: 

+

92 if e.code != ExitStatus.SUCCESS: 

+

93 env.stderr.write('\n') 

+

94 if include_traceback: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

+

95 raise 

+

96 exit_status = ExitStatus.ERROR 

+

97 else: 

+

98 check_updates(env) 

+

99 try: 

+

100 exit_status = main_program( 

+

101 args=parsed_args, 

+

102 env=env, 

+

103 ) 

+

104 except KeyboardInterrupt: 

+

105 env.stderr.write('\n') 

+

106 if include_traceback: 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true

+

107 raise 

+

108 exit_status = ExitStatus.ERROR_CTRL_C 

+

109 except SystemExit as e: 

+

110 if e.code != ExitStatus.SUCCESS: 110 ↛ 143line 110 didn't jump to line 143 because the condition on line 110 was always true

+

111 env.stderr.write('\n') 

+

112 if include_traceback: 112 ↛ 113line 112 didn't jump to line 113 because the condition on line 112 was never true

+

113 raise 

+

114 exit_status = ExitStatus.ERROR 

+

115 except requests.Timeout: 

+

116 exit_status = ExitStatus.ERROR_TIMEOUT 

+

117 env.log_error(f'Request timed out ({parsed_args.timeout}s).') 

+

118 except requests.TooManyRedirects: 

+

119 exit_status = ExitStatus.ERROR_TOO_MANY_REDIRECTS 

+

120 env.log_error( 

+

121 f'Too many redirects' 

+

122 f' (--max-redirects={parsed_args.max_redirects}).' 

+

123 ) 

+

124 except requests.exceptions.ConnectionError as exc: 

+

125 annotation = None 

+

126 original_exc = unwrap_context(exc) 

+

127 if isinstance(original_exc, socket.gaierror): 

+

128 if original_exc.errno == socket.EAI_AGAIN: 

+

129 annotation = '\nCouldn’t connect to a DNS server. Please check your connection and try again.' 

+

130 elif original_exc.errno == socket.EAI_NONAME: 130 ↛ 132line 130 didn't jump to line 132 because the condition on line 130 was always true

+

131 annotation = '\nCouldn’t resolve the given hostname. Please check the URL and try again.' 

+

132 propagated_exc = original_exc 

+

133 else: 

+

134 propagated_exc = exc 

+

135 

+

136 handle_generic_error(propagated_exc, annotation=annotation) 

+

137 exit_status = ExitStatus.ERROR 

+

138 except Exception as e: 

+

139 # TODO: Further distinction between expected and unexpected errors. 

+

140 handle_generic_error(e) 

+

141 exit_status = ExitStatus.ERROR 

+

142 

+

143 return exit_status 

+

144 

+

145 

+

146def main( 

+

147 args: List[Union[str, bytes]] = sys.argv, 

+

148 env: Environment = Environment() 

+

149) -> ExitStatus: 

+

150 """ 

+

151 The main function. 

+

152 

+

153 Pre-process args, handle some special types of invocations, 

+

154 and run the main program with error handling. 

+

155 

+

156 Return exit status code. 

+

157 

+

158 """ 

+

159 

+

160 from .cli.definition import parser 

+

161 

+

162 return raw_main( 

+

163 parser=parser, 

+

164 main_program=program, 

+

165 args=args, 

+

166 env=env 

+

167 ) 

+

168 

+

169 

+

170def program(args: argparse.Namespace, env: Environment) -> ExitStatus: 

+

171 """ 

+

172 The main program without error handling. 

+

173 

+

174 """ 

+

175 # TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere. 

+

176 exit_status = ExitStatus.SUCCESS 

+

177 downloader = None 

+

178 initial_request: Optional[requests.PreparedRequest] = None 

+

179 final_response: Optional[requests.Response] = None 

+

180 processing_options = ProcessingOptions.from_raw_args(args) 

+

181 

+

182 def separate(): 

+

183 getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES) 

+

184 

+

185 def request_body_read_callback(chunk: bytes): 

+

186 should_pipe_to_stdout = bool( 

+

187 # Request body output desired 

+

188 OUT_REQ_BODY in args.output_options 

+

189 # & not `.read()` already pre-request (e.g., for compression) 

+

190 and initial_request 

+

191 # & non-EOF chunk 

+

192 and chunk 

+

193 ) 

+

194 if should_pipe_to_stdout: 

+

195 return write_raw_data( 

+

196 env, 

+

197 chunk, 

+

198 processing_options=processing_options, 

+

199 headers=initial_request.headers 

+

200 ) 

+

201 

+

202 try: 

+

203 if args.download: 

+

204 args.follow = True # --download implies --follow. 

+

205 downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume) 

+

206 downloader.pre_request(args.headers) 

+

207 messages = collect_messages(env, args=args, 

+

208 request_body_read_callback=request_body_read_callback) 

+

209 force_separator = False 

+

210 prev_with_body = False 

+

211 

+

212 # Process messages as they’re generated 

+

213 for message in messages: 

+

214 output_options = OutputOptions.from_message(message, args.output_options) 

+

215 

+

216 do_write_body = output_options.body 

+

217 if prev_with_body and output_options.any() and (force_separator or not env.stdout_isatty): 

+

218 # Separate after a previous message with body, if needed. See test_tokens.py. 

+

219 separate() 

+

220 force_separator = False 

+

221 if output_options.kind is RequestsMessageKind.REQUEST: 

+

222 if not initial_request: 

+

223 initial_request = message 

+

224 if output_options.body: 

+

225 is_streamed_upload = not isinstance(message.body, (str, bytes)) 

+

226 do_write_body = not is_streamed_upload 

+

227 force_separator = is_streamed_upload and env.stdout_isatty 

+

228 else: 

+

229 final_response = message 

+

230 if args.check_status or downloader: 

+

231 exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow) 

+

232 if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1): 

+

233 env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING) 

+

234 write_message( 

+

235 requests_message=message, 

+

236 env=env, 

+

237 output_options=output_options._replace( 

+

238 body=do_write_body 

+

239 ), 

+

240 processing_options=processing_options 

+

241 ) 

+

242 prev_with_body = output_options.body 

+

243 

+

244 # Cleanup 

+

245 if force_separator: 

+

246 separate() 

+

247 if downloader and exit_status == ExitStatus.SUCCESS: 

+

248 # Last response body download. 

+

249 download_stream, download_to = downloader.start( 

+

250 initial_url=initial_request.url, 

+

251 final_response=final_response, 

+

252 ) 

+

253 write_stream(stream=download_stream, outfile=download_to, flush=False) 

+

254 downloader.finish() 

+

255 if downloader.interrupted: 255 ↛ 256line 255 didn't jump to line 256 because the condition on line 255 was never true

+

256 exit_status = ExitStatus.ERROR 

+

257 env.log_error( 

+

258 f'Incomplete download: size={downloader.status.total_size};' 

+

259 f' downloaded={downloader.status.downloaded}' 

+

260 ) 

+

261 return exit_status 

+

262 

+

263 finally: 

+

264 if downloader and not downloader.finished: 264 ↛ 265line 264 didn't jump to line 265 because the condition on line 264 was never true

+

265 downloader.failed() 

+

266 if args.output_file and args.output_file_specified: 

+

267 args.output_file.close() 

+

268 

+

269 

+

270def print_debug_info(env: Environment): 

+

271 env.stderr.writelines([ 

+

272 f'HTTPie {httpie_version}\n', 

+

273 f'Requests {requests_version}\n', 

+

274 f'Pygments {pygments_version}\n', 

+

275 f'Python {sys.version}\n{sys.executable}\n', 

+

276 f'{platform.system()} {platform.release()}', 

+

277 ]) 

+

278 env.stderr.write('\n\n') 

+

279 env.stderr.write(repr(env)) 

+

280 env.stderr.write('\n\n') 

+

281 env.stderr.write(repr(plugin_manager)) 

+

282 env.stderr.write('\n') 

+

283 

+

284 

+

285def decode_raw_args( 

+

286 args: List[Union[str, bytes]], 

+

287 stdin_encoding: str 

+

288) -> List[str]: 

+

289 """ 

+

290 Convert all bytes args to str 

+

291 by decoding them using stdin encoding. 

+

292 

+

293 """ 

+

294 return [ 

+

295 arg.decode(stdin_encoding) 

+

296 if type(arg) is bytes else arg 

+

297 for arg in args 

+

298 ] 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_downloads_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_downloads_py.html new file mode 100644 index 0000000000..d11c7106ac --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_downloads_py.html @@ -0,0 +1,476 @@ + + + + + Coverage for httpie\downloads.py: 93% + + + + + +
+
+

+ Coverage for httpie\downloads.py: + 93% +

+ +

+ 175 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2Download mode implementation. 

+

3 

+

4""" 

+

5import mimetypes 

+

6import os 

+

7import re 

+

8from mailbox import Message 

+

9from time import monotonic 

+

10from typing import IO, Optional, Tuple 

+

11from urllib.parse import urlsplit 

+

12 

+

13import requests 

+

14 

+

15from .models import HTTPResponse, OutputOptions 

+

16from .output.streams import RawStream 

+

17from .context import Environment 

+

18 

+

19 

+

20PARTIAL_CONTENT = 206 

+

21 

+

22 

+

23class ContentRangeError(ValueError): 

+

24 pass 

+

25 

+

26 

+

27def parse_content_range(content_range: str, resumed_from: int) -> int: 

+

28 """ 

+

29 Parse and validate Content-Range header. 

+

30 

+

31 <https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html> 

+

32 

+

33 :param content_range: the value of a Content-Range response header 

+

34 eg. "bytes 21010-47021/47022" 

+

35 :param resumed_from: first byte pos. from the Range request header 

+

36 :return: total size of the response body when fully downloaded. 

+

37 

+

38 """ 

+

39 if content_range is None: 

+

40 raise ContentRangeError('Missing Content-Range') 

+

41 

+

42 pattern = ( 

+

43 r'^bytes (?P<first_byte_pos>\d+)-(?P<last_byte_pos>\d+)' 

+

44 r'/(\*|(?P<instance_length>\d+))$' 

+

45 ) 

+

46 match = re.match(pattern, content_range) 

+

47 

+

48 if not match: 

+

49 raise ContentRangeError( 

+

50 f'Invalid Content-Range format {content_range!r}') 

+

51 

+

52 content_range_dict = match.groupdict() 

+

53 first_byte_pos = int(content_range_dict['first_byte_pos']) 

+

54 last_byte_pos = int(content_range_dict['last_byte_pos']) 

+

55 instance_length = ( 

+

56 int(content_range_dict['instance_length']) 

+

57 if content_range_dict['instance_length'] 

+

58 else None 

+

59 ) 

+

60 

+

61 # "A byte-content-range-spec with a byte-range-resp-spec whose 

+

62 # last- byte-pos value is less than its first-byte-pos value, 

+

63 # or whose instance-length value is less than or equal to its 

+

64 # last-byte-pos value, is invalid. The recipient of an invalid 

+

65 # byte-content-range- spec MUST ignore it and any content 

+

66 # transferred along with it." 

+

67 if (first_byte_pos > last_byte_pos 

+

68 or (instance_length is not None 

+

69 and instance_length <= last_byte_pos)): 

+

70 raise ContentRangeError( 

+

71 f'Invalid Content-Range returned: {content_range!r}') 

+

72 

+

73 if (first_byte_pos != resumed_from 

+

74 or (instance_length is not None 

+

75 and last_byte_pos + 1 != instance_length)): 

+

76 # Not what we asked for. 

+

77 raise ContentRangeError( 

+

78 f'Unexpected Content-Range returned ({content_range!r})' 

+

79 f' for the requested Range ("bytes={resumed_from}-")' 

+

80 ) 

+

81 

+

82 return last_byte_pos + 1 

+

83 

+

84 

+

85def filename_from_content_disposition( 

+

86 content_disposition: str 

+

87) -> Optional[str]: 

+

88 """ 

+

89 Extract and validate filename from a Content-Disposition header. 

+

90 

+

91 :param content_disposition: Content-Disposition value 

+

92 :return: the filename if present and valid, otherwise `None` 

+

93 

+

94 """ 

+

95 # attachment; filename=jakubroztocil-httpie-0.4.1-20-g40bd8f6.tar.gz 

+

96 

+

97 msg = Message(f'Content-Disposition: {content_disposition}') 

+

98 filename = msg.get_filename() 

+

99 if filename: 

+

100 # Basic sanitation. 

+

101 filename = os.path.basename(filename).lstrip('.').strip() 

+

102 if filename: 102 ↛ exitline 102 didn't return from function 'filename_from_content_disposition' because the condition on line 102 was always true

+

103 return filename 

+

104 

+

105 

+

106def filename_from_url(url: str, content_type: Optional[str]) -> str: 

+

107 fn = urlsplit(url).path.rstrip('/') 

+

108 fn = os.path.basename(fn) if fn else 'index' 

+

109 if '.' not in fn and content_type: 

+

110 content_type = content_type.split(';')[0] 

+

111 if content_type == 'text/plain': 

+

112 # mimetypes returns '.ksh' 

+

113 ext = '.txt' 

+

114 else: 

+

115 ext = mimetypes.guess_extension(content_type) 

+

116 

+

117 if ext == '.htm': 117 ↛ 118line 117 didn't jump to line 118 because the condition on line 117 was never true

+

118 ext = '.html' 

+

119 

+

120 if ext: 

+

121 fn += ext 

+

122 

+

123 return fn 

+

124 

+

125 

+

126def trim_filename(filename: str, max_len: int) -> str: 

+

127 if len(filename) > max_len: 127 ↛ 134line 127 didn't jump to line 134 because the condition on line 127 was always true

+

128 trim_by = len(filename) - max_len 

+

129 name, ext = os.path.splitext(filename) 

+

130 if trim_by >= len(name): 

+

131 filename = filename[:-trim_by] 

+

132 else: 

+

133 filename = name[:-trim_by] + ext 

+

134 return filename 

+

135 

+

136 

+

137def get_filename_max_length(directory: str) -> int: 

+

138 max_len = 255 

+

139 if hasattr(os, 'pathconf') and 'PC_NAME_MAX' in os.pathconf_names: 139 ↛ 140line 139 didn't jump to line 140 because the condition on line 139 was never true

+

140 max_len = os.pathconf(directory, 'PC_NAME_MAX') 

+

141 return max_len 

+

142 

+

143 

+

144def trim_filename_if_needed(filename: str, directory='.', extra=0) -> str: 

+

145 max_len = get_filename_max_length(directory) - extra 

+

146 if len(filename) > max_len: 

+

147 filename = trim_filename(filename, max_len) 

+

148 return filename 

+

149 

+

150 

+

151def get_unique_filename(filename: str, exists=os.path.exists) -> str: 

+

152 attempt = 0 

+

153 while True: 

+

154 suffix = f'-{attempt}' if attempt > 0 else '' 

+

155 try_filename = trim_filename_if_needed(filename, extra=len(suffix)) 

+

156 try_filename += suffix 

+

157 if not exists(try_filename): 

+

158 return try_filename 

+

159 attempt += 1 

+

160 

+

161 

+

162class Downloader: 

+

163 

+

164 def __init__( 

+

165 self, 

+

166 env: Environment, 

+

167 output_file: IO = None, 

+

168 resume: bool = False 

+

169 ): 

+

170 """ 

+

171 :param resume: Should the download resume if partial download 

+

172 already exists. 

+

173 

+

174 :param output_file: The file to store response body in. If not 

+

175 provided, it will be guessed from the response. 

+

176 

+

177 :param progress_file: Where to report download progress. 

+

178 

+

179 """ 

+

180 self.finished = False 

+

181 self.status = DownloadStatus(env=env) 

+

182 self._output_file = output_file 

+

183 self._resume = resume 

+

184 self._resumed_from = 0 

+

185 

+

186 def pre_request(self, request_headers: dict): 

+

187 """Called just before the HTTP request is sent. 

+

188 

+

189 Might alter `request_headers`. 

+

190 

+

191 """ 

+

192 # Ask the server not to encode the content so that we can resume, etc. 

+

193 request_headers['Accept-Encoding'] = 'identity' 

+

194 if self._resume: 

+

195 bytes_have = os.path.getsize(self._output_file.name) 

+

196 if bytes_have: 196 ↛ exitline 196 didn't return from function 'pre_request' because the condition on line 196 was always true

+

197 # Set ``Range`` header to resume the download 

+

198 # TODO: Use "If-Range: mtime" to make sure it's fresh? 

+

199 request_headers['Range'] = f'bytes={bytes_have}-' 

+

200 self._resumed_from = bytes_have 

+

201 

+

202 def start( 

+

203 self, 

+

204 initial_url: str, 

+

205 final_response: requests.Response 

+

206 ) -> Tuple[RawStream, IO]: 

+

207 """ 

+

208 Initiate and return a stream for `response` body with progress 

+

209 callback attached. Can be called only once. 

+

210 

+

211 :param initial_url: The original requested URL 

+

212 :param final_response: Initiated response object with headers already fetched 

+

213 

+

214 :return: RawStream, output_file 

+

215 

+

216 """ 

+

217 assert not self.status.time_started 

+

218 

+

219 # FIXME: some servers still might sent Content-Encoding: gzip 

+

220 # <https://github.com/httpie/cli/issues/423> 

+

221 try: 

+

222 total_size = int(final_response.headers['Content-Length']) 

+

223 except (KeyError, ValueError, TypeError): 

+

224 total_size = None 

+

225 

+

226 if not self._output_file: 

+

227 self._output_file = self._get_output_file_from_response( 

+

228 initial_url=initial_url, 

+

229 final_response=final_response, 

+

230 ) 

+

231 else: 

+

232 # `--output, -o` provided 

+

233 if self._resume and final_response.status_code == PARTIAL_CONTENT: 

+

234 total_size = parse_content_range( 

+

235 final_response.headers.get('Content-Range'), 

+

236 self._resumed_from 

+

237 ) 

+

238 

+

239 else: 

+

240 self._resumed_from = 0 

+

241 try: 

+

242 self._output_file.seek(0) 

+

243 self._output_file.truncate() 

+

244 except OSError: 

+

245 pass # stdout 

+

246 

+

247 output_options = OutputOptions.from_message(final_response, headers=False, body=True) 

+

248 stream = RawStream( 

+

249 msg=HTTPResponse(final_response), 

+

250 output_options=output_options, 

+

251 on_body_chunk_downloaded=self.chunk_downloaded, 

+

252 ) 

+

253 

+

254 self.status.started( 

+

255 output_file=self._output_file, 

+

256 resumed_from=self._resumed_from, 

+

257 total_size=total_size 

+

258 ) 

+

259 

+

260 return stream, self._output_file 

+

261 

+

262 def finish(self): 

+

263 assert not self.finished 

+

264 self.finished = True 

+

265 self.status.finished() 

+

266 

+

267 def failed(self): 

+

268 self.status.terminate() 

+

269 

+

270 @property 

+

271 def interrupted(self) -> bool: 

+

272 return ( 

+

273 self.finished 

+

274 and self.status.total_size 

+

275 and self.status.total_size != self.status.downloaded 

+

276 ) 

+

277 

+

278 def chunk_downloaded(self, chunk: bytes): 

+

279 """ 

+

280 A download progress callback. 

+

281 

+

282 :param chunk: A chunk of response body data that has just 

+

283 been downloaded and written to the output. 

+

284 

+

285 """ 

+

286 self.status.chunk_downloaded(len(chunk)) 

+

287 

+

288 @staticmethod 

+

289 def _get_output_file_from_response( 

+

290 initial_url: str, 

+

291 final_response: requests.Response, 

+

292 ) -> IO: 

+

293 # Output file not specified. Pick a name that doesn't exist yet. 

+

294 filename = None 

+

295 if 'Content-Disposition' in final_response.headers: 

+

296 filename = filename_from_content_disposition( 

+

297 final_response.headers['Content-Disposition']) 

+

298 if not filename: 

+

299 filename = filename_from_url( 

+

300 url=initial_url, 

+

301 content_type=final_response.headers.get('Content-Type'), 

+

302 ) 

+

303 unique_filename = get_unique_filename(filename) 

+

304 return open(unique_filename, buffering=0, mode='a+b') 

+

305 

+

306 

+

307class DownloadStatus: 

+

308 """Holds details about the download status.""" 

+

309 

+

310 def __init__(self, env): 

+

311 self.env = env 

+

312 self.downloaded = 0 

+

313 self.total_size = None 

+

314 self.resumed_from = 0 

+

315 self.time_started = None 

+

316 self.time_finished = None 

+

317 

+

318 def started(self, output_file, resumed_from=0, total_size=None): 

+

319 assert self.time_started is None 

+

320 self.total_size = total_size 

+

321 self.downloaded = self.resumed_from = resumed_from 

+

322 self.time_started = monotonic() 

+

323 self.start_display(output_file=output_file) 

+

324 

+

325 def start_display(self, output_file): 

+

326 from httpie.output.ui.rich_progress import ( 

+

327 DummyDisplay, 

+

328 StatusDisplay, 

+

329 ProgressDisplay 

+

330 ) 

+

331 

+

332 message = f'Downloading to {output_file.name}' 

+

333 if self.env.show_displays: 

+

334 if self.total_size is None: 334 ↛ 337line 334 didn't jump to line 337 because the condition on line 334 was never true

+

335 # Rich does not support progress bars without a total 

+

336 # size given. Instead we use status objects. 

+

337 self.display = StatusDisplay(self.env) 

+

338 else: 

+

339 self.display = ProgressDisplay(self.env) 

+

340 else: 

+

341 self.display = DummyDisplay(self.env) 

+

342 

+

343 self.display.start( 

+

344 total=self.total_size, 

+

345 at=self.downloaded, 

+

346 description=message 

+

347 ) 

+

348 

+

349 def chunk_downloaded(self, size): 

+

350 assert self.time_finished is None 

+

351 self.downloaded += size 

+

352 self.display.update(size) 

+

353 

+

354 @property 

+

355 def has_finished(self): 

+

356 return self.time_finished is not None 

+

357 

+

358 @property 

+

359 def time_spent(self): 

+

360 if ( 360 ↛ 366line 360 didn't jump to line 366 because the condition on line 360 was always true

+

361 self.time_started is not None 

+

362 and self.time_finished is not None 

+

363 ): 

+

364 return self.time_finished - self.time_started 

+

365 else: 

+

366 return None 

+

367 

+

368 def finished(self): 

+

369 assert self.time_started is not None 

+

370 assert self.time_finished is None 

+

371 self.time_finished = monotonic() 

+

372 if hasattr(self, 'display'): 372 ↛ exitline 372 didn't return from function 'finished' because the condition on line 372 was always true

+

373 self.display.stop(self.time_spent) 

+

374 

+

375 def terminate(self): 

+

376 if hasattr(self, 'display'): 376 ↛ exitline 376 didn't return from function 'terminate' because the condition on line 376 was always true

+

377 self.display.stop(self.time_spent) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_encoding_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_encoding_py.html new file mode 100644 index 0000000000..b64d83362d --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_encoding_py.html @@ -0,0 +1,149 @@ + + + + + Coverage for httpie\encoding.py: 96% + + + + + +
+
+

+ Coverage for httpie\encoding.py: + 96% +

+ +

+ 18 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Union, Tuple 

+

2 

+

3from charset_normalizer import from_bytes 

+

4from charset_normalizer.constant import TOO_SMALL_SEQUENCE 

+

5 

+

6UTF8 = 'utf-8' 

+

7 

+

8ContentBytes = Union[bytearray, bytes] 

+

9 

+

10 

+

11def detect_encoding(content: ContentBytes) -> str: 

+

12 """ 

+

13 We default to UTF-8 if text too short, because the detection 

+

14 can return a random encoding leading to confusing results 

+

15 given the `charset_normalizer` version (< 2.0.5). 

+

16 

+

17 >>> too_short = ']"foo"' 

+

18 >>> detected = from_bytes(too_short.encode()).best().encoding 

+

19 >>> detected 

+

20 'ascii' 

+

21 >>> too_short.encode().decode(detected) 

+

22 ']"foo"' 

+

23 """ 

+

24 encoding = UTF8 

+

25 if len(content) > TOO_SMALL_SEQUENCE: 

+

26 match = from_bytes(bytes(content)).best() 

+

27 if match: 27 ↛ 29line 27 didn't jump to line 29 because the condition on line 27 was always true

+

28 encoding = match.encoding 

+

29 return encoding 

+

30 

+

31 

+

32def smart_decode(content: ContentBytes, encoding: str) -> Tuple[str, str]: 

+

33 """Decode `content` using the given `encoding`. 

+

34 If no `encoding` is provided, the best effort is to guess it from `content`. 

+

35 

+

36 Unicode errors are replaced. 

+

37 

+

38 """ 

+

39 if not encoding: 

+

40 encoding = detect_encoding(content) 

+

41 return content.decode(encoding, 'replace'), encoding 

+

42 

+

43 

+

44def smart_encode(content: str, encoding: str) -> bytes: 

+

45 """Encode `content` using the given `encoding`. 

+

46 

+

47 Unicode errors are replaced. 

+

48 

+

49 """ 

+

50 return content.encode(encoding, 'replace') 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_models_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_models_py.html new file mode 100644 index 0000000000..e8f3a13982 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_models_py.html @@ -0,0 +1,332 @@ + + + + + Coverage for httpie\models.py: 95% + + + + + +
+
+

+ Coverage for httpie\models.py: + 95% +

+ +

+ 113 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from time import monotonic 

+

2 

+

3import requests 

+

4from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS 

+

5 

+

6from enum import Enum, auto 

+

7from typing import Iterable, Union, NamedTuple 

+

8from urllib.parse import urlsplit 

+

9 

+

10from .cli.constants import ( 

+

11 OUT_REQ_BODY, 

+

12 OUT_REQ_HEAD, 

+

13 OUT_RESP_BODY, 

+

14 OUT_RESP_HEAD, 

+

15 OUT_RESP_META 

+

16) 

+

17from .compat import cached_property 

+

18from .utils import split_cookies, parse_content_type_header 

+

19 

+

20ELAPSED_TIME_LABEL = 'Elapsed time' 

+

21 

+

22 

+

23class HTTPMessage: 

+

24 """Abstract class for HTTP messages.""" 

+

25 

+

26 def __init__(self, orig): 

+

27 self._orig = orig 

+

28 

+

29 def iter_body(self, chunk_size: int) -> Iterable[bytes]: 

+

30 """Return an iterator over the body.""" 

+

31 raise NotImplementedError 

+

32 

+

33 def iter_lines(self, chunk_size: int) -> Iterable[bytes]: 

+

34 """Return an iterator over the body yielding (`line`, `line_feed`).""" 

+

35 raise NotImplementedError 

+

36 

+

37 @property 

+

38 def headers(self) -> str: 

+

39 """Return a `str` with the message's headers.""" 

+

40 raise NotImplementedError 

+

41 

+

42 @property 

+

43 def metadata(self) -> str: 

+

44 """Return metadata about the current message.""" 

+

45 raise NotImplementedError 

+

46 

+

47 @cached_property 

+

48 def encoding(self) -> str: 

+

49 ct, params = parse_content_type_header(self.content_type) 

+

50 return params.get('charset', '') 

+

51 

+

52 @property 

+

53 def content_type(self) -> str: 

+

54 """Return the message content type.""" 

+

55 ct = self._orig.headers.get('Content-Type', '') 

+

56 if not isinstance(ct, str): 

+

57 ct = ct.decode() 

+

58 return ct 

+

59 

+

60 

+

61class HTTPResponse(HTTPMessage): 

+

62 """A :class:`requests.models.Response` wrapper.""" 

+

63 

+

64 def iter_body(self, chunk_size=1): 

+

65 return self._orig.iter_content(chunk_size=chunk_size) 

+

66 

+

67 def iter_lines(self, chunk_size): 

+

68 return ((line, b'\n') for line in self._orig.iter_lines(chunk_size)) 

+

69 

+

70 @property 

+

71 def headers(self): 

+

72 original = self._orig 

+

73 status_line = f'HTTP/{self.version} {original.status_code} {original.reason}' 

+

74 headers = [status_line] 

+

75 headers.extend( 

+

76 ': '.join(header) 

+

77 for header in original.headers.items() 

+

78 if header[0] != 'Set-Cookie' 

+

79 ) 

+

80 headers.extend( 

+

81 f'Set-Cookie: {cookie}' 

+

82 for header, value in original.headers.items() 

+

83 for cookie in split_cookies(value) 

+

84 if header == 'Set-Cookie' 

+

85 ) 

+

86 return '\r\n'.join(headers) 

+

87 

+

88 @property 

+

89 def metadata(self) -> str: 

+

90 data = {} 

+

91 time_to_parse_headers = self._orig.elapsed.total_seconds() 

+

92 # noinspection PyProtectedMember 

+

93 time_since_headers_parsed = monotonic() - self._orig._httpie_headers_parsed_at 

+

94 time_elapsed = time_to_parse_headers + time_since_headers_parsed 

+

95 # data['Headers time'] = str(round(time_to_parse_headers, 5)) + 's' 

+

96 # data['Body time'] = str(round(time_since_headers_parsed, 5)) + 's' 

+

97 data[ELAPSED_TIME_LABEL] = str(round(time_elapsed, 10)) + 's' 

+

98 return '\n'.join( 

+

99 f'{key}: {value}' 

+

100 for key, value in data.items() 

+

101 ) 

+

102 

+

103 @property 

+

104 def version(self) -> str: 

+

105 """ 

+

106 Return the HTTP version used by the server, e.g. '1.1'. 

+

107 

+

108 Assume HTTP/1.1 if version is not available. 

+

109 

+

110 """ 

+

111 mapping = { 

+

112 9: '0.9', 

+

113 10: '1.0', 

+

114 11: '1.1', 

+

115 20: '2.0', 

+

116 } 

+

117 fallback = 11 

+

118 version = None 

+

119 try: 

+

120 raw = self._orig.raw 

+

121 if getattr(raw, '_original_response', None): 

+

122 version = raw._original_response.version 

+

123 else: 

+

124 version = raw.version 

+

125 except AttributeError: 

+

126 pass 

+

127 return mapping[version or fallback] 

+

128 

+

129 

+

130class HTTPRequest(HTTPMessage): 

+

131 """A :class:`requests.models.Request` wrapper.""" 

+

132 

+

133 def iter_body(self, chunk_size): 

+

134 yield self.body 

+

135 

+

136 def iter_lines(self, chunk_size): 

+

137 yield self.body, b'' 

+

138 

+

139 @property 

+

140 def headers(self): 

+

141 url = urlsplit(self._orig.url) 

+

142 

+

143 request_line = '{method} {path}{query} HTTP/1.1'.format( 

+

144 method=self._orig.method, 

+

145 path=url.path or '/', 

+

146 query=f'?{url.query}' if url.query else '' 

+

147 ) 

+

148 

+

149 headers = self._orig.headers.copy() 

+

150 if 'Host' not in self._orig.headers: 

+

151 headers['Host'] = url.netloc.split('@')[-1] 

+

152 

+

153 headers = [ 

+

154 f'{name}: {value if isinstance(value, str) else value.decode()}' 

+

155 for name, value in headers.items() 

+

156 if not (name.lower() in SKIPPABLE_HEADERS and value == SKIP_HEADER) 

+

157 ] 

+

158 

+

159 headers.insert(0, request_line) 

+

160 headers = '\r\n'.join(headers).strip() 

+

161 return headers 

+

162 

+

163 @property 

+

164 def body(self): 

+

165 body = self._orig.body 

+

166 if isinstance(body, str): 

+

167 # Happens with JSON/form request data parsed from the command line. 

+

168 body = body.encode() 

+

169 return body or b'' 

+

170 

+

171 

+

172RequestsMessage = Union[requests.PreparedRequest, requests.Response] 

+

173 

+

174 

+

175class RequestsMessageKind(Enum): 

+

176 REQUEST = auto() 

+

177 RESPONSE = auto() 

+

178 

+

179 

+

180def infer_requests_message_kind(message: RequestsMessage) -> RequestsMessageKind: 

+

181 if isinstance(message, requests.PreparedRequest): 

+

182 return RequestsMessageKind.REQUEST 

+

183 elif isinstance(message, requests.Response): 183 ↛ 186line 183 didn't jump to line 186 because the condition on line 183 was always true

+

184 return RequestsMessageKind.RESPONSE 

+

185 else: 

+

186 raise TypeError(f"Unexpected message type: {type(message).__name__}") 

+

187 

+

188 

+

189OPTION_TO_PARAM = { 

+

190 RequestsMessageKind.REQUEST: { 

+

191 'headers': OUT_REQ_HEAD, 

+

192 'body': OUT_REQ_BODY, 

+

193 }, 

+

194 RequestsMessageKind.RESPONSE: { 

+

195 'headers': OUT_RESP_HEAD, 

+

196 'body': OUT_RESP_BODY, 

+

197 'meta': OUT_RESP_META 

+

198 } 

+

199} 

+

200 

+

201 

+

202class OutputOptions(NamedTuple): 

+

203 kind: RequestsMessageKind 

+

204 headers: bool 

+

205 body: bool 

+

206 meta: bool = False 

+

207 

+

208 def any(self): 

+

209 return ( 

+

210 self.headers 

+

211 or self.body 

+

212 or self.meta 

+

213 ) 

+

214 

+

215 @classmethod 

+

216 def from_message( 

+

217 cls, 

+

218 message: RequestsMessage, 

+

219 raw_args: str = '', 

+

220 **kwargs 

+

221 ): 

+

222 kind = infer_requests_message_kind(message) 

+

223 

+

224 options = { 

+

225 option: param in raw_args 

+

226 for option, param in OPTION_TO_PARAM[kind].items() 

+

227 } 

+

228 options.update(kwargs) 

+

229 

+

230 return cls( 

+

231 kind=kind, 

+

232 **options 

+

233 ) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_sessions_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_sessions_py.html new file mode 100644 index 0000000000..ef308ed5c9 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_sessions_py.html @@ -0,0 +1,421 @@ + + + + + Coverage for httpie\sessions.py: 98% + + + + + +
+
+

+ Coverage for httpie\sessions.py: + 98% +

+ +

+ 155 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2Persistent, JSON-serialized sessions. 

+

3 

+

4""" 

+

5import os 

+

6import re 

+

7 

+

8from http.cookies import SimpleCookie 

+

9from http.cookiejar import Cookie 

+

10from pathlib import Path 

+

11from typing import Any, Dict, List, Optional, Union 

+

12 

+

13from requests.auth import AuthBase 

+

14from requests.cookies import RequestsCookieJar, remove_cookie_by_name 

+

15 

+

16from .context import Environment, LogLevel 

+

17from .cookies import HTTPieCookiePolicy 

+

18from .cli.dicts import HTTPHeadersDict 

+

19from .config import BaseConfigDict, DEFAULT_CONFIG_DIR 

+

20from .utils import url_as_host 

+

21from .plugins.registry import plugin_manager 

+

22 

+

23from .legacy import ( 

+

24 v3_1_0_session_cookie_format as legacy_cookies, 

+

25 v3_2_0_session_header_format as legacy_headers 

+

26) 

+

27 

+

28 

+

29SESSIONS_DIR_NAME = 'sessions' 

+

30DEFAULT_SESSIONS_DIR = DEFAULT_CONFIG_DIR / SESSIONS_DIR_NAME 

+

31VALID_SESSION_NAME_PATTERN = re.compile('^[a-zA-Z0-9_.-]+$') 

+

32# Request headers starting with these prefixes won't be stored in sessions. 

+

33# They are specific to each request. 

+

34# <https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Requests> 

+

35SESSION_IGNORED_HEADER_PREFIXES = ['Content-', 'If-'] 

+

36 

+

37# Cookie related options 

+

38KEPT_COOKIE_OPTIONS = ['name', 'expires', 'path', 'value', 'domain', 'secure'] 

+

39DEFAULT_COOKIE_PATH = '/' 

+

40 

+

41 

+

42def is_anonymous_session(session_name: str) -> bool: 

+

43 return os.path.sep in session_name 

+

44 

+

45 

+

46def session_hostname_to_dirname(hostname: str, session_name: str) -> str: 

+

47 # host:port => host_port 

+

48 hostname = hostname.replace(':', '_') 

+

49 return os.path.join( 

+

50 SESSIONS_DIR_NAME, 

+

51 hostname, 

+

52 f'{session_name}.json' 

+

53 ) 

+

54 

+

55 

+

56def strip_port(hostname: str) -> str: 

+

57 return hostname.split(':')[0] 

+

58 

+

59 

+

60def materialize_cookie(cookie: Cookie) -> Dict[str, Any]: 

+

61 materialized_cookie = { 

+

62 option: getattr(cookie, option) 

+

63 for option in KEPT_COOKIE_OPTIONS 

+

64 } 

+

65 

+

66 if ( 

+

67 cookie._rest.get('is_explicit_none') 

+

68 and materialized_cookie['domain'] == '' 

+

69 ): 

+

70 materialized_cookie['domain'] = None 

+

71 

+

72 return materialized_cookie 

+

73 

+

74 

+

75def materialize_cookies(jar: RequestsCookieJar) -> List[Dict[str, Any]]: 

+

76 return [ 

+

77 materialize_cookie(cookie) 

+

78 for cookie in jar 

+

79 ] 

+

80 

+

81 

+

82def materialize_headers(headers: Dict[str, str]) -> List[Dict[str, Any]]: 

+

83 return [ 

+

84 { 

+

85 'name': name, 

+

86 'value': value 

+

87 } 

+

88 for name, value in headers.copy().items() 

+

89 ] 

+

90 

+

91 

+

92def get_httpie_session( 

+

93 env: Environment, 

+

94 config_dir: Path, 

+

95 session_name: str, 

+

96 host: Optional[str], 

+

97 url: str, 

+

98 *, 

+

99 suppress_legacy_warnings: bool = False 

+

100) -> 'Session': 

+

101 bound_hostname = host or url_as_host(url) 

+

102 if not bound_hostname: 102 ↛ 104line 102 didn't jump to line 104 because the condition on line 102 was never true

+

103 # HACK/FIXME: httpie-unixsocket's URLs have no hostname. 

+

104 bound_hostname = 'localhost' 

+

105 

+

106 if is_anonymous_session(session_name): 

+

107 path = os.path.expanduser(session_name) 

+

108 session_id = path 

+

109 else: 

+

110 path = config_dir / session_hostname_to_dirname(bound_hostname, session_name) 

+

111 session_id = session_name 

+

112 

+

113 session = Session( 

+

114 path, 

+

115 env=env, 

+

116 session_id=session_id, 

+

117 bound_host=strip_port(bound_hostname), 

+

118 suppress_legacy_warnings=suppress_legacy_warnings 

+

119 ) 

+

120 session.load() 

+

121 return session 

+

122 

+

123 

+

124class Session(BaseConfigDict): 

+

125 helpurl = 'https://httpie.io/docs#sessions' 

+

126 about = 'HTTPie session file' 

+

127 

+

128 def __init__( 

+

129 self, 

+

130 path: Union[str, Path], 

+

131 env: Environment, 

+

132 bound_host: str, 

+

133 session_id: str, 

+

134 suppress_legacy_warnings: bool = False, 

+

135 ): 

+

136 super().__init__(path=Path(path)) 

+

137 

+

138 # Default values for the session files 

+

139 self['headers'] = [] 

+

140 self['cookies'] = [] 

+

141 self['auth'] = { 

+

142 'type': None, 

+

143 'username': None, 

+

144 'password': None 

+

145 } 

+

146 

+

147 # Runtime state of the Session objects. 

+

148 self.env = env 

+

149 self._headers = HTTPHeadersDict() 

+

150 self.cookie_jar = RequestsCookieJar( 

+

151 # See also a temporary workaround for a Requests bug in `compat.py`. 

+

152 policy=HTTPieCookiePolicy(), 

+

153 ) 

+

154 self.session_id = session_id 

+

155 self.bound_host = bound_host 

+

156 self.suppress_legacy_warnings = suppress_legacy_warnings 

+

157 

+

158 def _add_cookies(self, cookies: List[Dict[str, Any]]) -> None: 

+

159 for cookie in cookies: 

+

160 domain = cookie.get('domain', '') 

+

161 if domain is None: 

+

162 # domain = None means explicitly lack of cookie, though 

+

163 # requests requires domain to be a string so we'll cast it 

+

164 # manually. 

+

165 cookie['domain'] = '' 

+

166 cookie['rest'] = {'is_explicit_none': True} 

+

167 

+

168 self.cookie_jar.set(**cookie) 

+

169 

+

170 def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]: 

+

171 for key, deserializer, importer in [ 

+

172 ('cookies', legacy_cookies.pre_process, self._add_cookies), 

+

173 ('headers', legacy_headers.pre_process, self._headers.update), 

+

174 ]: 

+

175 values = data.get(key) 

+

176 if values: 

+

177 normalized_values = deserializer(self, values) 

+

178 else: 

+

179 normalized_values = [] 

+

180 

+

181 importer(normalized_values) 

+

182 

+

183 return data 

+

184 

+

185 def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]: 

+

186 for key, store, serializer, exporter in [ 

+

187 ('cookies', self.cookie_jar, materialize_cookies, legacy_cookies.post_process), 

+

188 ('headers', self._headers, materialize_headers, legacy_headers.post_process), 

+

189 ]: 

+

190 original_type = type(data.get(key)) 

+

191 values = serializer(store) 

+

192 

+

193 data[key] = exporter( 

+

194 values, 

+

195 original_type=original_type 

+

196 ) 

+

197 

+

198 return data 

+

199 

+

200 def _compute_new_headers(self, request_headers: HTTPHeadersDict) -> HTTPHeadersDict: 

+

201 new_headers = HTTPHeadersDict() 

+

202 for name, value in request_headers.copy().items(): 

+

203 if value is None: 

+

204 continue # Ignore explicitly unset headers 

+

205 

+

206 original_value = value 

+

207 if type(value) is not str: 

+

208 value = value.decode() 

+

209 

+

210 if name.lower() == 'user-agent' and value.startswith('HTTPie/'): 

+

211 continue 

+

212 

+

213 if name.lower() == 'cookie': 

+

214 for cookie_name, morsel in SimpleCookie(value).items(): 

+

215 if not morsel['path']: 215 ↛ 217line 215 didn't jump to line 217 because the condition on line 215 was always true

+

216 morsel['path'] = DEFAULT_COOKIE_PATH 

+

217 self.cookie_jar.set(cookie_name, morsel) 

+

218 

+

219 request_headers.remove_item(name, original_value) 

+

220 continue 

+

221 

+

222 for prefix in SESSION_IGNORED_HEADER_PREFIXES: 

+

223 if name.lower().startswith(prefix.lower()): 

+

224 break 

+

225 else: 

+

226 new_headers.add(name, value) 

+

227 

+

228 return new_headers 

+

229 

+

230 def update_headers(self, request_headers: HTTPHeadersDict): 

+

231 """ 

+

232 Update the session headers with the request ones while ignoring 

+

233 certain name prefixes. 

+

234 

+

235 """ 

+

236 

+

237 new_headers = self._compute_new_headers(request_headers) 

+

238 new_keys = new_headers.copy().keys() 

+

239 

+

240 # New headers will take priority over the existing ones, and override 

+

241 # them directly instead of extending them. 

+

242 for key, value in self._headers.copy().items(): 

+

243 if key in new_keys: 

+

244 continue 

+

245 

+

246 new_headers.add(key, value) 

+

247 

+

248 self._headers = new_headers 

+

249 

+

250 @property 

+

251 def headers(self) -> HTTPHeadersDict: 

+

252 return self._headers.copy() 

+

253 

+

254 @property 

+

255 def cookies(self) -> RequestsCookieJar: 

+

256 self.cookie_jar.clear_expired_cookies() 

+

257 return self.cookie_jar 

+

258 

+

259 @cookies.setter 

+

260 def cookies(self, jar: RequestsCookieJar): 

+

261 self.cookie_jar = jar 

+

262 

+

263 def remove_cookies(self, cookies: List[Dict[str, str]]): 

+

264 for cookie in cookies: 

+

265 remove_cookie_by_name( 

+

266 self.cookie_jar, 

+

267 cookie['name'], 

+

268 domain=cookie.get('domain', None), 

+

269 path=cookie.get('path', None) 

+

270 ) 

+

271 

+

272 @property 

+

273 def auth(self) -> Optional[AuthBase]: 

+

274 auth = self.get('auth', None) 

+

275 if not auth or not auth['type']: 

+

276 return 

+

277 

+

278 plugin = plugin_manager.get_auth_plugin(auth['type'])() 

+

279 

+

280 credentials = {'username': None, 'password': None} 

+

281 try: 

+

282 # New style 

+

283 plugin.raw_auth = auth['raw_auth'] 

+

284 except KeyError: 

+

285 # Old style 

+

286 credentials = { 

+

287 'username': auth['username'], 

+

288 'password': auth['password'], 

+

289 } 

+

290 else: 

+

291 if plugin.auth_parse: 

+

292 from .cli.argtypes import parse_auth 

+

293 parsed = parse_auth(plugin.raw_auth) 

+

294 credentials = { 

+

295 'username': parsed.key, 

+

296 'password': parsed.value, 

+

297 } 

+

298 

+

299 return plugin.get_auth(**credentials) 

+

300 

+

301 @auth.setter 

+

302 def auth(self, auth: dict): 

+

303 assert {'type', 'raw_auth'} == auth.keys() 

+

304 self['auth'] = auth 

+

305 

+

306 @property 

+

307 def is_anonymous(self): 

+

308 return is_anonymous_session(self.session_id) 

+

309 

+

310 def warn_legacy_usage(self, warning: str) -> None: 

+

311 if self.suppress_legacy_warnings: 

+

312 return None 

+

313 

+

314 self.env.log_error( 

+

315 warning, 

+

316 level=LogLevel.WARNING 

+

317 ) 

+

318 

+

319 # We don't want to spam multiple warnings on each usage, 

+

320 # so if there is already a warning for the legacy usage 

+

321 # we'll skip the next ones. 

+

322 self.suppress_legacy_warnings = True 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_ssl__py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_ssl__py.html new file mode 100644 index 0000000000..6b6dfbaeba --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_ssl__py.html @@ -0,0 +1,208 @@ + + + + + Coverage for httpie\ssl_.py: 96% + + + + + +
+
+

+ Coverage for httpie\ssl_.py: + 96% +

+ +

+ 43 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import ssl 

+

2from typing import NamedTuple, Optional 

+

3 

+

4# noinspection PyPackageRequirements 

+

5from urllib3.util.ssl_ import ( 

+

6 create_urllib3_context, 

+

7 resolve_ssl_version, 

+

8) 

+

9 

+

10from .adapters import HTTPAdapter 

+

11from .compat import ensure_default_certs_loaded 

+

12 

+

13 

+

14SSL_VERSION_ARG_MAPPING = { 

+

15 'ssl2.3': 'PROTOCOL_SSLv23', 

+

16 'ssl3': 'PROTOCOL_SSLv3', 

+

17 'tls1': 'PROTOCOL_TLSv1', 

+

18 'tls1.1': 'PROTOCOL_TLSv1_1', 

+

19 'tls1.2': 'PROTOCOL_TLSv1_2', 

+

20 'tls1.3': 'PROTOCOL_TLSv1_3', 

+

21} 

+

22AVAILABLE_SSL_VERSION_ARG_MAPPING = { 

+

23 arg: getattr(ssl, constant_name) 

+

24 for arg, constant_name in SSL_VERSION_ARG_MAPPING.items() 

+

25 if hasattr(ssl, constant_name) 

+

26} 

+

27 

+

28 

+

29class HTTPieCertificate(NamedTuple): 

+

30 cert_file: Optional[str] = None 

+

31 key_file: Optional[str] = None 

+

32 key_password: Optional[str] = None 

+

33 

+

34 def to_raw_cert(self): 

+

35 """Synthesize a requests-compatible (2-item tuple of cert and key file) 

+

36 object from HTTPie's internal representation of a certificate.""" 

+

37 return self.cert_file, self.key_file 

+

38 

+

39 

+

40class HTTPieHTTPSAdapter(HTTPAdapter): 

+

41 def __init__( 

+

42 self, 

+

43 verify: bool, 

+

44 ssl_version: str = None, 

+

45 ciphers: str = None, 

+

46 **kwargs 

+

47 ): 

+

48 self._ssl_context = self._create_ssl_context( 

+

49 verify=verify, 

+

50 ssl_version=ssl_version, 

+

51 ciphers=ciphers, 

+

52 ) 

+

53 super().__init__(**kwargs) 

+

54 

+

55 def init_poolmanager(self, *args, **kwargs): 

+

56 kwargs['ssl_context'] = self._ssl_context 

+

57 return super().init_poolmanager(*args, **kwargs) 

+

58 

+

59 def proxy_manager_for(self, *args, **kwargs): 

+

60 kwargs['ssl_context'] = self._ssl_context 

+

61 return super().proxy_manager_for(*args, **kwargs) 

+

62 

+

63 def cert_verify(self, conn, url, verify, cert): 

+

64 if isinstance(cert, HTTPieCertificate): 

+

65 conn.key_password = cert.key_password 

+

66 cert = cert.to_raw_cert() 

+

67 

+

68 return super().cert_verify(conn, url, verify, cert) 

+

69 

+

70 @staticmethod 

+

71 def _create_ssl_context( 

+

72 verify: bool, 

+

73 ssl_version: str = None, 

+

74 ciphers: str = None, 

+

75 ) -> 'ssl.SSLContext': 

+

76 ssl_context = create_urllib3_context( 

+

77 ciphers=ciphers, 

+

78 ssl_version=resolve_ssl_version(ssl_version), 

+

79 # Since we are using a custom SSL context, we need to pass this 

+

80 # here manually, even though it’s also passed to the connection 

+

81 # in `super().cert_verify()`. 

+

82 cert_reqs=ssl.CERT_REQUIRED if verify else ssl.CERT_NONE 

+

83 ) 

+

84 ensure_default_certs_loaded(ssl_context) 

+

85 return ssl_context 

+

86 

+

87 @classmethod 

+

88 def get_default_ciphers_names(cls): 

+

89 return [cipher['name'] for cipher in cls._create_ssl_context(verify=False).get_ciphers()] 

+

90 

+

91 

+

92def _is_key_file_encrypted(key_file): 

+

93 """Detects if a key file is encrypted or not. 

+

94 

+

95 Copy of the internal urllib function (urllib3.util.ssl_)""" 

+

96 

+

97 with open(key_file, "r") as f: 

+

98 for line in f: 

+

99 # Look for Proc-Type: 4,ENCRYPTED 

+

100 if "ENCRYPTED" in line: 

+

101 return True 

+

102 

+

103 return False 

+

104 

+

105 

+

106# We used to import the default set of TLS ciphers from urllib3, but they removed it. 

+

107# Instead, now urllib3 uses the list of ciphers configured by the system. 

+

108# <https://github.com/httpie/cli/pull/1501> 

+

109DEFAULT_SSL_CIPHERS_STRING = ':'.join(HTTPieHTTPSAdapter.get_default_ciphers_names()) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_status_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_status_py.html new file mode 100644 index 0000000000..f3e4b1b69c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_status_py.html @@ -0,0 +1,139 @@ + + + + + Coverage for httpie\status.py: 100% + + + + + +
+
+

+ Coverage for httpie\status.py: + 100% +

+ +

+ 20 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from enum import IntEnum, unique 

+

2 

+

3 

+

4@unique 

+

5class ExitStatus(IntEnum): 

+

6 """Program exit status code constants.""" 

+

7 SUCCESS = 0 

+

8 ERROR = 1 

+

9 ERROR_TIMEOUT = 2 

+

10 

+

11 # See --check-status 

+

12 ERROR_HTTP_3XX = 3 

+

13 ERROR_HTTP_4XX = 4 

+

14 ERROR_HTTP_5XX = 5 

+

15 

+

16 ERROR_TOO_MANY_REDIRECTS = 6 

+

17 PLUGIN_ERROR = 7 

+

18 # 128+2 SIGINT 

+

19 # <http://www.tldp.org/LDP/abs/html/exitcodes.html> 

+

20 ERROR_CTRL_C = 130 

+

21 

+

22 

+

23def http_status_to_exit_status(http_status: int, follow=False) -> ExitStatus: 

+

24 """ 

+

25 Translate HTTP status code to exit status code. 

+

26 

+

27 (Relevant only when invoked with --check-status or --download.) 

+

28 

+

29 """ 

+

30 if 300 <= http_status <= 399 and not follow: 

+

31 # Redirect 

+

32 return ExitStatus.ERROR_HTTP_3XX 

+

33 elif 400 <= http_status <= 499: 

+

34 # Client Error 

+

35 return ExitStatus.ERROR_HTTP_4XX 

+

36 elif 500 <= http_status <= 599: 

+

37 # Server Error 

+

38 return ExitStatus.ERROR_HTTP_5XX 

+

39 else: 

+

40 return ExitStatus.SUCCESS 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_uploads_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_uploads_py.html new file mode 100644 index 0000000000..9f85271ecd --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_uploads_py.html @@ -0,0 +1,368 @@ + + + + + Coverage for httpie\uploads.py: 84% + + + + + +
+
+

+ Coverage for httpie\uploads.py: + 84% +

+ +

+ 130 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import sys 

+

2import os 

+

3import zlib 

+

4import functools 

+

5import threading 

+

6from typing import Any, Callable, IO, Iterable, Optional, Tuple, Union, TYPE_CHECKING 

+

7from urllib.parse import urlencode 

+

8 

+

9import requests 

+

10from requests.utils import super_len 

+

11 

+

12if TYPE_CHECKING: 

+

13 from requests_toolbelt import MultipartEncoder 

+

14 

+

15from .context import Environment 

+

16from .cli.dicts import MultipartRequestDataDict, RequestDataDict 

+

17from .compat import is_windows 

+

18 

+

19 

+

20class ChunkedStream: 

+

21 def __iter__(self) -> Iterable[Union[str, bytes]]: 

+

22 raise NotImplementedError 

+

23 

+

24 

+

25class ChunkedUploadStream(ChunkedStream): 

+

26 def __init__( 

+

27 self, 

+

28 stream: Iterable, 

+

29 callback: Callable, 

+

30 event: Optional[threading.Event] = None 

+

31 ) -> None: 

+

32 self.callback = callback 

+

33 self.stream = stream 

+

34 self.event = event 

+

35 

+

36 def __iter__(self) -> Iterable[Union[str, bytes]]: 

+

37 for chunk in self.stream: 

+

38 if self.event: 

+

39 self.event.set() 

+

40 self.callback(chunk) 

+

41 yield chunk 

+

42 

+

43 

+

44class ChunkedMultipartUploadStream(ChunkedStream): 

+

45 chunk_size = 100 * 1024 

+

46 

+

47 def __init__( 

+

48 self, 

+

49 encoder: 'MultipartEncoder', 

+

50 event: Optional[threading.Event] = None 

+

51 ) -> None: 

+

52 self.encoder = encoder 

+

53 self.event = event 

+

54 

+

55 def __iter__(self) -> Iterable[Union[str, bytes]]: 

+

56 while True: 

+

57 chunk = self.encoder.read(self.chunk_size) 

+

58 if self.event: 58 ↛ 60line 58 didn't jump to line 60 because the condition on line 58 was always true

+

59 self.event.set() 

+

60 if not chunk: 

+

61 break 

+

62 yield chunk 

+

63 

+

64 

+

65def as_bytes(data: Union[str, bytes]) -> bytes: 

+

66 if isinstance(data, str): 

+

67 return data.encode() 

+

68 else: 

+

69 return data 

+

70 

+

71 

+

72CallbackT = Callable[[bytes], bytes] 

+

73 

+

74 

+

75def _wrap_function_with_callback( 

+

76 func: Callable[..., Any], 

+

77 callback: CallbackT 

+

78) -> Callable[..., Any]: 

+

79 @functools.wraps(func) 

+

80 def wrapped(*args, **kwargs): 

+

81 chunk = func(*args, **kwargs) 

+

82 callback(chunk) 

+

83 return chunk 

+

84 return wrapped 

+

85 

+

86 

+

87def is_stdin(file: IO) -> bool: 

+

88 try: 

+

89 file_no = file.fileno() 

+

90 except Exception: 

+

91 return False 

+

92 else: 

+

93 return file_no == sys.stdin.fileno() 

+

94 

+

95 

+

96READ_THRESHOLD = float(os.getenv('HTTPIE_STDIN_READ_WARN_THRESHOLD', 10.0)) 

+

97 

+

98 

+

99def observe_stdin_for_data_thread(env: Environment, file: IO, read_event: threading.Event) -> None: 

+

100 # Windows unfortunately does not support select() operation 

+

101 # on regular files, like stdin in our use case. 

+

102 # https://docs.python.org/3/library/select.html#select.select 

+

103 if is_windows: 

+

104 return None 

+

105 

+

106 # If the user configures READ_THRESHOLD to be 0, then 

+

107 # disable this warning. 

+

108 if READ_THRESHOLD == 0: 

+

109 return None 

+

110 

+

111 def worker(event: threading.Event) -> None: 

+

112 if not event.wait(timeout=READ_THRESHOLD): 

+

113 env.stderr.write( 

+

114 f'> warning: no stdin data read in {READ_THRESHOLD}s ' 

+

115 f'(perhaps you want to --ignore-stdin)\n' 

+

116 f'> See: https://httpie.io/docs/cli/best-practices\n' 

+

117 ) 

+

118 

+

119 # Making it a daemon ensures that if the user exits from the main program 

+

120 # (e.g. either regularly or with Ctrl-C), the thread will not 

+

121 # block them. 

+

122 thread = threading.Thread( 

+

123 target=worker, 

+

124 args=(read_event,), 

+

125 daemon=True 

+

126 ) 

+

127 thread.start() 

+

128 

+

129 

+

130def _read_file_with_selectors(file: IO, read_event: threading.Event) -> bytes: 

+

131 if is_windows or not is_stdin(file): 131 ↛ 134line 131 didn't jump to line 134 because the condition on line 131 was always true

+

132 return as_bytes(file.read()) 

+

133 

+

134 import select 

+

135 

+

136 # Try checking whether there is any incoming data for READ_THRESHOLD 

+

137 # seconds. If there isn't anything in the given period, issue 

+

138 # a warning about a misusage. 

+

139 read_selectors, _, _ = select.select([file], [], [], READ_THRESHOLD) 

+

140 if read_selectors: 

+

141 read_event.set() 

+

142 

+

143 return as_bytes(file.read()) 

+

144 

+

145 

+

146def _prepare_file_for_upload( 

+

147 env: Environment, 

+

148 file: Union[IO, 'MultipartEncoder'], 

+

149 callback: CallbackT, 

+

150 chunked: bool = False, 

+

151 content_length_header_value: Optional[int] = None, 

+

152) -> Union[bytes, IO, ChunkedStream]: 

+

153 read_event = threading.Event() 

+

154 if not super_len(file): 

+

155 if is_stdin(file): 155 ↛ 156line 155 didn't jump to line 156 because the condition on line 155 was never true

+

156 observe_stdin_for_data_thread(env, file, read_event) 

+

157 

+

158 # Zero-length -> assume stdin. 

+

159 if content_length_header_value is None and not chunked: 

+

160 # Read the whole stdin to determine `Content-Length`. 

+

161 # 

+

162 # TODO: Instead of opt-in --chunked, consider making 

+

163 # `Transfer-Encoding: chunked` for STDIN opt-out via 

+

164 # something like --no-chunked. 

+

165 # This would be backwards-incompatible so wait until v3.0.0. 

+

166 # 

+

167 file = _read_file_with_selectors(file, read_event) 

+

168 else: 

+

169 file.read = _wrap_function_with_callback( 

+

170 file.read, 

+

171 callback 

+

172 ) 

+

173 

+

174 if chunked: 

+

175 from requests_toolbelt import MultipartEncoder 

+

176 if isinstance(file, MultipartEncoder): 

+

177 return ChunkedMultipartUploadStream( 

+

178 encoder=file, 

+

179 event=read_event, 

+

180 ) 

+

181 else: 

+

182 return ChunkedUploadStream( 

+

183 stream=file, 

+

184 callback=callback, 

+

185 event=read_event 

+

186 ) 

+

187 else: 

+

188 return file 

+

189 

+

190 

+

191def prepare_request_body( 

+

192 env: Environment, 

+

193 raw_body: Union[str, bytes, IO, 'MultipartEncoder', RequestDataDict], 

+

194 body_read_callback: CallbackT, 

+

195 offline: bool = False, 

+

196 chunked: bool = False, 

+

197 content_length_header_value: Optional[int] = None, 

+

198) -> Union[bytes, IO, 'MultipartEncoder', ChunkedStream]: 

+

199 is_file_like = hasattr(raw_body, 'read') 

+

200 if isinstance(raw_body, (bytes, str)): 

+

201 body = as_bytes(raw_body) 

+

202 elif isinstance(raw_body, RequestDataDict): 

+

203 body = as_bytes(urlencode(raw_body, doseq=True)) 

+

204 else: 

+

205 body = raw_body 

+

206 

+

207 if offline: 

+

208 if is_file_like: 

+

209 return as_bytes(raw_body.read()) 

+

210 else: 

+

211 return body 

+

212 

+

213 if is_file_like: 

+

214 return _prepare_file_for_upload( 

+

215 env, 

+

216 body, 

+

217 chunked=chunked, 

+

218 callback=body_read_callback, 

+

219 content_length_header_value=content_length_header_value 

+

220 ) 

+

221 elif chunked: 

+

222 return ChunkedUploadStream( 

+

223 stream=iter([body]), 

+

224 callback=body_read_callback 

+

225 ) 

+

226 else: 

+

227 return body 

+

228 

+

229 

+

230def get_multipart_data_and_content_type( 

+

231 data: MultipartRequestDataDict, 

+

232 boundary: str = None, 

+

233 content_type: str = None, 

+

234) -> Tuple['MultipartEncoder', str]: 

+

235 from requests_toolbelt import MultipartEncoder 

+

236 

+

237 encoder = MultipartEncoder( 

+

238 fields=data.items(), 

+

239 boundary=boundary, 

+

240 ) 

+

241 if content_type: 

+

242 content_type = content_type.strip() 

+

243 if 'boundary=' not in content_type: 

+

244 content_type = f'{content_type}; boundary={encoder.boundary_value}' 

+

245 else: 

+

246 content_type = encoder.content_type 

+

247 

+

248 data = encoder 

+

249 return data, content_type 

+

250 

+

251 

+

252def compress_request( 

+

253 request: requests.PreparedRequest, 

+

254 always: bool, 

+

255): 

+

256 deflater = zlib.compressobj() 

+

257 if isinstance(request.body, str): 257 ↛ 258line 257 didn't jump to line 258 because the condition on line 257 was never true

+

258 body_bytes = request.body.encode() 

+

259 elif hasattr(request.body, 'read'): 

+

260 body_bytes = request.body.read() 

+

261 else: 

+

262 body_bytes = request.body 

+

263 deflated_data = deflater.compress(body_bytes) 

+

264 deflated_data += deflater.flush() 

+

265 is_economical = len(deflated_data) < len(body_bytes) 

+

266 if is_economical or always: 

+

267 request.body = deflated_data 

+

268 request.headers['Content-Encoding'] = 'deflate' 

+

269 request.headers['Content-Length'] = str(len(deflated_data)) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_utils_py.html b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_utils_py.html new file mode 100644 index 0000000000..1304fb1160 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_4802d84346c9dc29_utils_py.html @@ -0,0 +1,408 @@ + + + + + Coverage for httpie\utils.py: 84% + + + + + +
+
+

+ Coverage for httpie\utils.py: + 84% +

+ +

+ 131 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import os 

+

2import base64 

+

3import json 

+

4import mimetypes 

+

5import re 

+

6import sys 

+

7import time 

+

8import tempfile 

+

9import sysconfig 

+

10 

+

11from collections import OrderedDict 

+

12from contextlib import contextmanager 

+

13from http.cookiejar import parse_ns_headers 

+

14from pathlib import Path 

+

15from pprint import pformat 

+

16from urllib.parse import urlsplit 

+

17from typing import Any, List, Optional, Tuple, Generator, Callable, Iterable, IO, TypeVar 

+

18 

+

19import requests.auth 

+

20 

+

21RE_COOKIE_SPLIT = re.compile(r', (?=[^ ;]+=)') 

+

22Item = Tuple[str, Any] 

+

23Items = List[Item] 

+

24T = TypeVar("T") 

+

25 

+

26 

+

27class JsonDictPreservingDuplicateKeys(OrderedDict): 

+

28 """A specialized JSON dict preserving duplicate keys.""" 

+

29 

+

30 # Python versions prior to 3.8 suffer from an issue with multiple keys with the same name. 

+

31 # `json.dumps(obj, indent=N, sort_keys=True)` will output sorted keys when they are unique, and 

+

32 # duplicate keys will be outputted as they were defined in the original data. 

+

33 # See <https://bugs.python.org/issue23493#msg400929> for the behavior change between Python versions. 

+

34 SUPPORTS_SORTING = sys.version_info >= (3, 8) 

+

35 

+

36 def __init__(self, items: Items): 

+

37 self._items = items 

+

38 self._ensure_items_used() 

+

39 

+

40 def _ensure_items_used(self) -> None: 

+

41 """HACK: Force `json.dumps()` to use `self.items()` instead of an empty dict. 

+

42 

+

43 Two JSON encoders are available on CPython: pure-Python (1) and C (2) implementations. 

+

44 

+

45 (1) The pure-python implementation will do a simple `if not dict: return '{}'`, 

+

46 and we could fake that check by implementing the `__bool__()` method. 

+

47 Source: 

+

48 - <https://github.com/python/cpython/blob/9d318ad/Lib/json/encoder.py#L334-L336> 

+

49 

+

50 (2) On the other hand, the C implementation will do a check on the number of 

+

51 items contained inside the dict, using a verification on `dict->ma_used`, which 

+

52 is updated only when an item is added/removed from the dict. For that case, 

+

53 there is no workaround but to add an item into the dict. 

+

54 Sources: 

+

55 - <https://github.com/python/cpython/blob/9d318ad/Modules/_json.c#L1581-L1582> 

+

56 - <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L53> 

+

57 - <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L17-L18> 

+

58 

+

59 To please both implementations, we simply add one item to the dict. 

+

60 

+

61 """ 

+

62 if self._items: 

+

63 self['__hack__'] = '__hack__' 

+

64 

+

65 def items(self) -> Items: 

+

66 """Return all items, duplicate ones included. 

+

67 

+

68 """ 

+

69 return self._items 

+

70 

+

71 

+

72def load_json_preserve_order_and_dupe_keys(s): 

+

73 return json.loads(s, object_pairs_hook=JsonDictPreservingDuplicateKeys) 

+

74 

+

75 

+

76def repr_dict(d: dict) -> str: 

+

77 return pformat(d) 

+

78 

+

79 

+

80def humanize_bytes(n, precision=2): 

+

81 # Author: Doug Latornell 

+

82 # Licence: MIT 

+

83 # URL: https://code.activestate.com/recipes/577081/ 

+

84 """Return a humanized string representation of a number of bytes. 

+

85 

+

86 >>> humanize_bytes(1) 

+

87 '1 B' 

+

88 >>> humanize_bytes(1024, precision=1) 

+

89 '1.0 kB' 

+

90 >>> humanize_bytes(1024 * 123, precision=1) 

+

91 '123.0 kB' 

+

92 >>> humanize_bytes(1024 * 12342, precision=1) 

+

93 '12.1 MB' 

+

94 >>> humanize_bytes(1024 * 12342, precision=2) 

+

95 '12.05 MB' 

+

96 >>> humanize_bytes(1024 * 1234, precision=2) 

+

97 '1.21 MB' 

+

98 >>> humanize_bytes(1024 * 1234 * 1111, precision=2) 

+

99 '1.31 GB' 

+

100 >>> humanize_bytes(1024 * 1234 * 1111, precision=1) 

+

101 '1.3 GB' 

+

102 

+

103 """ 

+

104 abbrevs = [ 

+

105 (1 << 50, 'PB'), 

+

106 (1 << 40, 'TB'), 

+

107 (1 << 30, 'GB'), 

+

108 (1 << 20, 'MB'), 

+

109 (1 << 10, 'kB'), 

+

110 (1, 'B') 

+

111 ] 

+

112 

+

113 if n == 1: 

+

114 return '1 B' 

+

115 

+

116 for factor, suffix in abbrevs: 

+

117 if n >= factor: 

+

118 break 

+

119 

+

120 # noinspection PyUnboundLocalVariable 

+

121 return f'{n / factor:.{precision}f} {suffix}' 

+

122 

+

123 

+

124class ExplicitNullAuth(requests.auth.AuthBase): 

+

125 """Forces requests to ignore the ``.netrc``. 

+

126 <https://github.com/psf/requests/issues/2773#issuecomment-174312831> 

+

127 """ 

+

128 

+

129 def __call__(self, r): 

+

130 return r 

+

131 

+

132 

+

133def get_content_type(filename): 

+

134 """ 

+

135 Return the content type for ``filename`` in format appropriate 

+

136 for Content-Type headers, or ``None`` if the file type is unknown 

+

137 to ``mimetypes``. 

+

138 

+

139 """ 

+

140 return mimetypes.guess_type(filename, strict=False)[0] 

+

141 

+

142 

+

143def split_cookies(cookies): 

+

144 """ 

+

145 When ``requests`` stores cookies in ``response.headers['Set-Cookie']`` 

+

146 it concatenates all of them through ``, ``. 

+

147 

+

148 This function splits cookies apart being careful to not to 

+

149 split on ``, `` which may be part of cookie value. 

+

150 """ 

+

151 if not cookies: 

+

152 return [] 

+

153 return RE_COOKIE_SPLIT.split(cookies) 

+

154 

+

155 

+

156def get_expired_cookies( 

+

157 cookies: str, 

+

158 now: float = None 

+

159) -> List[dict]: 

+

160 

+

161 now = now or time.time() 

+

162 

+

163 def is_expired(expires: Optional[float]) -> bool: 

+

164 return expires is not None and expires <= now 

+

165 

+

166 attr_sets: List[Tuple[str, str]] = parse_ns_headers( 

+

167 split_cookies(cookies) 

+

168 ) 

+

169 

+

170 cookies = [ 

+

171 # The first attr name is the cookie name. 

+

172 dict(attrs[1:], name=attrs[0][0]) 

+

173 for attrs in attr_sets 

+

174 ] 

+

175 

+

176 _max_age_to_expires(cookies=cookies, now=now) 

+

177 

+

178 return [ 

+

179 { 

+

180 'name': cookie['name'], 

+

181 'path': cookie.get('path', '/') 

+

182 } 

+

183 for cookie in cookies 

+

184 if is_expired(expires=cookie.get('expires')) 

+

185 ] 

+

186 

+

187 

+

188def _max_age_to_expires(cookies, now): 

+

189 """ 

+

190 Translate `max-age` into `expires` for Requests to take it into account. 

+

191 

+

192 HACK/FIXME: <https://github.com/psf/requests/issues/5743> 

+

193 

+

194 """ 

+

195 for cookie in cookies: 

+

196 if 'expires' in cookie: 

+

197 continue 

+

198 max_age = cookie.get('max-age') 

+

199 if max_age and max_age.isdigit(): 

+

200 cookie['expires'] = now + float(max_age) 

+

201 

+

202 

+

203def parse_content_type_header(header): 

+

204 """Borrowed from requests.""" 

+

205 tokens = header.split(';') 

+

206 content_type, params = tokens[0].strip(), tokens[1:] 

+

207 params_dict = {} 

+

208 items_to_strip = "\"' " 

+

209 for param in params: 

+

210 param = param.strip() 

+

211 if param: 211 ↛ 209line 211 didn't jump to line 209 because the condition on line 211 was always true

+

212 key, value = param, True 

+

213 index_of_equals = param.find("=") 

+

214 if index_of_equals != -1: 214 ↛ 217line 214 didn't jump to line 217 because the condition on line 214 was always true

+

215 key = param[:index_of_equals].strip(items_to_strip) 

+

216 value = param[index_of_equals + 1:].strip(items_to_strip) 

+

217 params_dict[key.lower()] = value 

+

218 return content_type, params_dict 

+

219 

+

220 

+

221def as_site(path: Path, **extra_vars) -> Path: 

+

222 site_packages_path = sysconfig.get_path( 

+

223 'purelib', 

+

224 vars={'base': str(path), **extra_vars} 

+

225 ) 

+

226 return Path(site_packages_path) 

+

227 

+

228 

+

229def get_site_paths(path: Path) -> Iterable[Path]: 

+

230 from httpie.compat import ( 

+

231 MIN_SUPPORTED_PY_VERSION, 

+

232 MAX_SUPPORTED_PY_VERSION, 

+

233 is_frozen 

+

234 ) 

+

235 

+

236 if is_frozen: 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true

+

237 [major, min_minor] = MIN_SUPPORTED_PY_VERSION 

+

238 [major, max_minor] = MAX_SUPPORTED_PY_VERSION 

+

239 for minor in range(min_minor, max_minor + 1): 

+

240 yield as_site( 

+

241 path, 

+

242 py_version_short=f'{major}.{minor}' 

+

243 ) 

+

244 else: 

+

245 yield as_site(path) 

+

246 

+

247 

+

248def split_iterable(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], List[T]]: 

+

249 left, right = [], [] 

+

250 for item in iterable: 

+

251 if key(item): 

+

252 left.append(item) 

+

253 else: 

+

254 right.append(item) 

+

255 return left, right 

+

256 

+

257 

+

258def unwrap_context(exc: Exception) -> Optional[Exception]: 

+

259 context = exc.__context__ 

+

260 if isinstance(context, Exception): 

+

261 return unwrap_context(context) 

+

262 else: 

+

263 return exc 

+

264 

+

265 

+

266def url_as_host(url: str) -> str: 

+

267 return urlsplit(url).netloc.split('@')[-1] 

+

268 

+

269 

+

270class LockFileError(ValueError): 

+

271 pass 

+

272 

+

273 

+

274@contextmanager 

+

275def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None, None]: 

+

276 file_id = base64.b64encode(os.fsencode(file)).decode() 

+

277 target_file = Path(tempfile.gettempdir()) / file_id 

+

278 

+

279 # Have an atomic-like touch here, so we'll tighten the possibility of 

+

280 # a race occurring between multiple processes accessing the same file. 

+

281 try: 

+

282 target_file.touch(exist_ok=False) 

+

283 except FileExistsError as exc: 

+

284 raise LockFileError("Can't modify a locked file.") from exc 

+

285 

+

286 try: 

+

287 with open(file, *args, **kwargs) as stream: 

+

288 yield stream 

+

289 finally: 

+

290 target_file.unlink() 

+

291 

+

292 

+

293def is_version_greater(version_1: str, version_2: str) -> bool: 

+

294 # In an ideal scenario, we would depend on `packaging` in order 

+

295 # to offer PEP 440 compatible parsing. But since it might not be 

+

296 # commonly available for outside packages, and since we are only 

+

297 # going to parse HTTPie's own version it should be fine to compare 

+

298 # this in a SemVer subset fashion. 

+

299 

+

300 def split_version(version: str) -> Tuple[int, ...]: 

+

301 parts = [] 

+

302 for part in version.split('.')[:3]: 

+

303 try: 

+

304 parts.append(int(part)) 

+

305 except ValueError: 

+

306 break 

+

307 return tuple(parts) 

+

308 

+

309 return split_version(version_1) > split_version(version_2) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4___init___py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4___init___py.html new file mode 100644 index 0000000000..8ea09ee228 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\output\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_models_py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_models_py.html new file mode 100644 index 0000000000..2e15e708d7 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_models_py.html @@ -0,0 +1,143 @@ + + + + + Coverage for httpie\output\models.py: 97% + + + + + +
+
+

+ Coverage for httpie\output\models.py: + 97% +

+ +

+ 27 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from typing import Any, Dict, Union, List, NamedTuple, Optional 

+

3 

+

4from httpie.context import Environment 

+

5from httpie.cli.constants import PrettyOptions, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY 

+

6from httpie.cli.argtypes import PARSED_DEFAULT_FORMAT_OPTIONS 

+

7from httpie.output.formatters.colors import AUTO_STYLE 

+

8 

+

9 

+

10class ProcessingOptions(NamedTuple): 

+

11 """Represents a set of stylistic options 

+

12 that are used when deciding which stream 

+

13 should be used.""" 

+

14 

+

15 debug: bool = False 

+

16 traceback: bool = False 

+

17 

+

18 stream: bool = False 

+

19 style: str = AUTO_STYLE 

+

20 prettify: Union[List[str], PrettyOptions] = PRETTY_STDOUT_TTY_ONLY 

+

21 

+

22 response_mime: Optional[str] = None 

+

23 response_charset: Optional[str] = None 

+

24 

+

25 json: bool = False 

+

26 format_options: Dict[str, Any] = PARSED_DEFAULT_FORMAT_OPTIONS 

+

27 

+

28 def get_prettify(self, env: Environment) -> List[str]: 

+

29 if self.prettify is PRETTY_STDOUT_TTY_ONLY: 

+

30 return PRETTY_MAP['all' if env.stdout_isatty else 'none'] 

+

31 else: 

+

32 return self.prettify 

+

33 

+

34 @classmethod 

+

35 def from_raw_args(cls, options: argparse.Namespace) -> 'ProcessingOptions': 

+

36 fetched_options = { 

+

37 option: getattr(options, option) 

+

38 for option in cls._fields 

+

39 } 

+

40 return cls(**fetched_options) 

+

41 

+

42 @property 

+

43 def show_traceback(self): 

+

44 return self.debug or self.traceback 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_processing_py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_processing_py.html new file mode 100644 index 0000000000..16bfba6aef --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_processing_py.html @@ -0,0 +1,157 @@ + + + + + Coverage for httpie\output\processing.py: 96% + + + + + +
+
+

+ Coverage for httpie\output\processing.py: + 96% +

+ +

+ 37 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import re 

+

2from typing import Optional, List 

+

3 

+

4from ..plugins import ConverterPlugin 

+

5from ..plugins.registry import plugin_manager 

+

6from ..context import Environment 

+

7 

+

8 

+

9MIME_RE = re.compile(r'^[^/]+/[^/]+$') 

+

10 

+

11 

+

12def is_valid_mime(mime): 

+

13 return mime and MIME_RE.match(mime) 

+

14 

+

15 

+

16class Conversion: 

+

17 

+

18 @staticmethod 

+

19 def get_converter(mime: str) -> Optional[ConverterPlugin]: 

+

20 if is_valid_mime(mime): 20 ↛ exitline 20 didn't return from function 'get_converter' because the condition on line 20 was always true

+

21 for converter_class in plugin_manager.get_converters(): 

+

22 if converter_class.supports(mime): 22 ↛ 21line 22 didn't jump to line 21 because the condition on line 22 was always true

+

23 return converter_class(mime) 

+

24 

+

25 

+

26class Formatting: 

+

27 """A delegate class that invokes the actual processors.""" 

+

28 

+

29 def __init__(self, groups: List[str], env=Environment(), **kwargs): 

+

30 """ 

+

31 :param groups: names of processor groups to be applied 

+

32 :param env: Environment 

+

33 :param kwargs: additional keyword arguments for processors 

+

34 

+

35 """ 

+

36 available_plugins = plugin_manager.get_formatters_grouped() 

+

37 self.enabled_plugins = [] 

+

38 for group in groups: 

+

39 for cls in available_plugins[group]: 

+

40 p = cls(env=env, **kwargs) 

+

41 if p.enabled: 

+

42 self.enabled_plugins.append(p) 

+

43 

+

44 def format_headers(self, headers: str) -> str: 

+

45 for p in self.enabled_plugins: 

+

46 headers = p.format_headers(headers) 

+

47 return headers 

+

48 

+

49 def format_body(self, content: str, mime: str) -> str: 

+

50 if is_valid_mime(mime): 

+

51 for p in self.enabled_plugins: 

+

52 content = p.format_body(content, mime) 

+

53 return content 

+

54 

+

55 def format_metadata(self, metadata: str) -> str: 

+

56 for p in self.enabled_plugins: 

+

57 metadata = p.format_metadata(metadata) 

+

58 return metadata 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_streams_py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_streams_py.html new file mode 100644 index 0000000000..175fd748ea --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_streams_py.html @@ -0,0 +1,353 @@ + + + + + Coverage for httpie\output\streams.py: 97% + + + + + +
+
+

+ Coverage for httpie\output\streams.py: + 97% +

+ +

+ 138 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from abc import ABCMeta, abstractmethod 

+

2from itertools import chain 

+

3from typing import Callable, Iterable, Optional, Union 

+

4 

+

5from .processing import Conversion, Formatting 

+

6from ..context import Environment 

+

7from ..encoding import smart_decode, smart_encode, UTF8 

+

8from ..models import HTTPMessage, OutputOptions 

+

9from ..utils import parse_content_type_header 

+

10 

+

11 

+

12BINARY_SUPPRESSED_NOTICE = ( 

+

13 b'\n' 

+

14 b'+-----------------------------------------+\n' 

+

15 b'| NOTE: binary data not shown in terminal |\n' 

+

16 b'+-----------------------------------------+' 

+

17) 

+

18 

+

19 

+

20class DataSuppressedError(Exception): 

+

21 message = None 

+

22 

+

23 

+

24class BinarySuppressedError(DataSuppressedError): 

+

25 """An error indicating that the body is binary and won't be written, 

+

26 e.g., for terminal output).""" 

+

27 message = BINARY_SUPPRESSED_NOTICE 

+

28 

+

29 

+

30class BaseStream(metaclass=ABCMeta): 

+

31 """Base HTTP message output stream class.""" 

+

32 

+

33 def __init__( 

+

34 self, 

+

35 msg: HTTPMessage, 

+

36 output_options: OutputOptions, 

+

37 on_body_chunk_downloaded: Callable[[bytes], None] = None, 

+

38 **kwargs 

+

39 ): 

+

40 """ 

+

41 :param msg: a :class:`models.HTTPMessage` subclass 

+

42 :param output_options: a :class:`OutputOptions` instance to represent 

+

43 which parts of the message is printed. 

+

44 """ 

+

45 assert output_options.any() 

+

46 self.msg = msg 

+

47 self.output_options = output_options 

+

48 self.on_body_chunk_downloaded = on_body_chunk_downloaded 

+

49 self.extra_options = kwargs 

+

50 

+

51 def get_headers(self) -> bytes: 

+

52 """Return the headers' bytes.""" 

+

53 return self.msg.headers.encode() 

+

54 

+

55 def get_metadata(self) -> bytes: 

+

56 """Return the message metadata.""" 

+

57 return self.msg.metadata.encode() 

+

58 

+

59 @abstractmethod 

+

60 def iter_body(self) -> Iterable[bytes]: 

+

61 """Return an iterator over the message body.""" 

+

62 

+

63 def __iter__(self) -> Iterable[bytes]: 

+

64 """Return an iterator over `self.msg`.""" 

+

65 if self.output_options.headers: 

+

66 yield self.get_headers() 

+

67 yield b'\r\n\r\n' 

+

68 

+

69 if self.output_options.body: 

+

70 try: 

+

71 for chunk in self.iter_body(): 

+

72 yield chunk 

+

73 if self.on_body_chunk_downloaded: 

+

74 self.on_body_chunk_downloaded(chunk) 

+

75 except DataSuppressedError as e: 

+

76 if self.output_options.headers: 

+

77 yield b'\n' 

+

78 yield e.message 

+

79 

+

80 if self.output_options.meta: 

+

81 if self.output_options.body: 

+

82 yield b'\n\n' 

+

83 

+

84 yield self.get_metadata() 

+

85 yield b'\n\n' 

+

86 

+

87 

+

88class RawStream(BaseStream): 

+

89 """The message is streamed in chunks with no processing.""" 

+

90 

+

91 CHUNK_SIZE = 1024 * 100 

+

92 CHUNK_SIZE_BY_LINE = 1 

+

93 

+

94 def __init__(self, chunk_size=CHUNK_SIZE, **kwargs): 

+

95 super().__init__(**kwargs) 

+

96 self.chunk_size = chunk_size 

+

97 

+

98 def iter_body(self) -> Iterable[bytes]: 

+

99 return self.msg.iter_body(self.chunk_size) 

+

100 

+

101 

+

102ENCODING_GUESS_THRESHOLD = 3 

+

103 

+

104 

+

105class EncodedStream(BaseStream): 

+

106 """Encoded HTTP message stream. 

+

107 

+

108 The message bytes are converted to an encoding suitable for 

+

109 `self.env.stdout`. Unicode errors are replaced and binary data 

+

110 is suppressed. The body is always streamed by line. 

+

111 

+

112 """ 

+

113 CHUNK_SIZE = 1 

+

114 

+

115 def __init__( 

+

116 self, 

+

117 env=Environment(), 

+

118 mime_overwrite: str = None, 

+

119 encoding_overwrite: str = None, 

+

120 **kwargs 

+

121 ): 

+

122 super().__init__(**kwargs) 

+

123 if mime_overwrite: 

+

124 self.mime = mime_overwrite 

+

125 else: 

+

126 self.mime, _ = parse_content_type_header(self.msg.content_type) 

+

127 self._encoding = encoding_overwrite or self.msg.encoding 

+

128 self._encoding_guesses = [] 

+

129 if env.stdout_isatty: 

+

130 # Use the encoding supported by the terminal. 

+

131 output_encoding = env.stdout_encoding 

+

132 else: 

+

133 # Preserve the message encoding. 

+

134 output_encoding = self.msg.encoding 

+

135 # Default to UTF-8 when unsure. 

+

136 self.output_encoding = output_encoding or UTF8 

+

137 

+

138 def iter_body(self) -> Iterable[bytes]: 

+

139 for line, lf in self.msg.iter_lines(self.CHUNK_SIZE): 

+

140 if b'\0' in line: 

+

141 raise BinarySuppressedError() 

+

142 line = self.decode_chunk(line) 

+

143 yield smart_encode(line, self.output_encoding) + lf 

+

144 

+

145 def decode_chunk(self, raw_chunk: str) -> str: 

+

146 chunk, guessed_encoding = smart_decode(raw_chunk, self.encoding) 

+

147 self._encoding_guesses.append(guessed_encoding) 

+

148 return chunk 

+

149 

+

150 @property 

+

151 def encoding(self) -> Optional[str]: 

+

152 if self._encoding: 

+

153 return self._encoding 

+

154 

+

155 # If we find a reliable (used consecutively) encoding, than 

+

156 # use it for the next iterations. 

+

157 if len(self._encoding_guesses) < ENCODING_GUESS_THRESHOLD: 

+

158 return None 

+

159 

+

160 guess_1, guess_2 = self._encoding_guesses[-2:] 

+

161 if guess_1 == guess_2: 

+

162 self._encoding = guess_1 

+

163 return guess_1 

+

164 

+

165 @encoding.setter 

+

166 def encoding(self, value) -> None: 

+

167 self._encoding = value 

+

168 

+

169 

+

170class PrettyStream(EncodedStream): 

+

171 """In addition to :class:`EncodedStream` behaviour, this stream applies 

+

172 content processing. 

+

173 

+

174 Useful for long-lived HTTP responses that stream by lines 

+

175 such as the Twitter streaming API. 

+

176 

+

177 """ 

+

178 

+

179 CHUNK_SIZE = 1 

+

180 

+

181 def __init__( 

+

182 self, conversion: Conversion, 

+

183 formatting: Formatting, 

+

184 **kwargs, 

+

185 ): 

+

186 super().__init__(**kwargs) 

+

187 self.formatting = formatting 

+

188 self.conversion = conversion 

+

189 

+

190 def get_headers(self) -> bytes: 

+

191 return self.formatting.format_headers( 

+

192 self.msg.headers).encode(self.output_encoding) 

+

193 

+

194 def get_metadata(self) -> bytes: 

+

195 return self.formatting.format_metadata( 

+

196 self.msg.metadata).encode(self.output_encoding) 

+

197 

+

198 def iter_body(self) -> Iterable[bytes]: 

+

199 first_chunk = True 

+

200 iter_lines = self.msg.iter_lines(self.CHUNK_SIZE) 

+

201 for line, lf in iter_lines: 

+

202 if b'\0' in line: 

+

203 if first_chunk: 203 ↛ 215line 203 didn't jump to line 215 because the condition on line 203 was always true

+

204 converter = self.conversion.get_converter(self.mime) 

+

205 if converter: 205 ↛ 215line 205 didn't jump to line 215 because the condition on line 205 was always true

+

206 body = bytearray() 

+

207 # noinspection PyAssignmentToLoopOrWithParameter 

+

208 for line, lf in chain([(line, lf)], iter_lines): 

+

209 body.extend(line) 

+

210 body.extend(lf) 

+

211 self.mime, body = converter.convert(body) 

+

212 assert isinstance(body, str) 

+

213 yield self.process_body(body) 

+

214 return 

+

215 raise BinarySuppressedError() 

+

216 yield self.process_body(line) + lf 

+

217 first_chunk = False 

+

218 

+

219 def process_body(self, chunk: Union[str, bytes]) -> bytes: 

+

220 if not isinstance(chunk, str): 

+

221 # Text when a converter has been used, 

+

222 # otherwise it will always be bytes. 

+

223 chunk = self.decode_chunk(chunk) 

+

224 chunk = self.formatting.format_body(content=chunk, mime=self.mime) 

+

225 return smart_encode(chunk, self.output_encoding) 

+

226 

+

227 

+

228class BufferedPrettyStream(PrettyStream): 

+

229 """The same as :class:`PrettyStream` except that the body is fully 

+

230 fetched before it's processed. 

+

231 

+

232 Suitable regular HTTP responses. 

+

233 

+

234 """ 

+

235 

+

236 CHUNK_SIZE = 1024 * 10 

+

237 

+

238 def iter_body(self) -> Iterable[bytes]: 

+

239 # Read the whole body before prettifying it, 

+

240 # but bail out immediately if the body is binary. 

+

241 converter = None 

+

242 body = bytearray() 

+

243 

+

244 for chunk in self.msg.iter_body(self.CHUNK_SIZE): 

+

245 if not converter and b'\0' in chunk: 

+

246 converter = self.conversion.get_converter(self.mime) 

+

247 if not converter: 

+

248 raise BinarySuppressedError() 

+

249 body.extend(chunk) 

+

250 

+

251 if converter: 

+

252 self.mime, body = converter.convert(body) 

+

253 

+

254 yield self.process_body(body) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_utils_py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_utils_py.html new file mode 100644 index 0000000000..76dd9eb0fe --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_utils_py.html @@ -0,0 +1,136 @@ + + + + + Coverage for httpie\output\utils.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\utils.py: + 100% +

+ +

+ 20 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import json 

+

2import re 

+

3from typing import Tuple 

+

4 

+

5from ..utils import load_json_preserve_order_and_dupe_keys 

+

6from .lexers.json import PREFIX_REGEX 

+

7 

+

8 

+

9def load_prefixed_json(data: str) -> Tuple[str, json.JSONDecoder]: 

+

10 """Simple JSON loading from `data`. 

+

11 

+

12 """ 

+

13 # First, the full data. 

+

14 try: 

+

15 return '', load_json_preserve_order_and_dupe_keys(data) 

+

16 except ValueError: 

+

17 pass 

+

18 

+

19 # Then, try to find the start of the actual body. 

+

20 data_prefix, body = parse_prefixed_json(data) 

+

21 try: 

+

22 return data_prefix, load_json_preserve_order_and_dupe_keys(body) 

+

23 except ValueError: 

+

24 raise ValueError('Invalid JSON') 

+

25 

+

26 

+

27def parse_prefixed_json(data: str) -> Tuple[str, str]: 

+

28 """Find the potential JSON body from `data`. 

+

29 

+

30 Sometimes the JSON body is prefixed with a XSSI magic string, specific to the server. 

+

31 Return a tuple (data prefix, actual JSON body). 

+

32 

+

33 """ 

+

34 matches = re.findall(PREFIX_REGEX, data) 

+

35 data_prefix = matches[0] if matches else '' 

+

36 body = data[len(data_prefix):] 

+

37 return data_prefix, body 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_writer_py.html b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_writer_py.html new file mode 100644 index 0000000000..aa6449d97b --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_63fc1e7d517295a4_writer_py.html @@ -0,0 +1,303 @@ + + + + + Coverage for httpie\output\writer.py: 79% + + + + + +
+
+

+ Coverage for httpie\output\writer.py: + 79% +

+ +

+ 76 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import errno 

+

2import requests 

+

3from typing import Any, Dict, IO, Optional, TextIO, Tuple, Type, Union 

+

4 

+

5from ..cli.dicts import HTTPHeadersDict 

+

6from ..context import Environment 

+

7from ..models import ( 

+

8 HTTPRequest, 

+

9 HTTPResponse, 

+

10 HTTPMessage, 

+

11 RequestsMessage, 

+

12 RequestsMessageKind, 

+

13 OutputOptions, 

+

14) 

+

15from .models import ProcessingOptions 

+

16from .processing import Conversion, Formatting 

+

17from .streams import ( 

+

18 BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream, 

+

19) 

+

20from ..utils import parse_content_type_header 

+

21 

+

22 

+

23MESSAGE_SEPARATOR = '\n\n' 

+

24MESSAGE_SEPARATOR_BYTES = MESSAGE_SEPARATOR.encode() 

+

25 

+

26 

+

27def write_message( 

+

28 requests_message: RequestsMessage, 

+

29 env: Environment, 

+

30 output_options: OutputOptions, 

+

31 processing_options: ProcessingOptions, 

+

32 extra_stream_kwargs: Optional[Dict[str, Any]] = None 

+

33): 

+

34 if not output_options.any(): 

+

35 return 

+

36 write_stream_kwargs = { 

+

37 'stream': build_output_stream_for_message( 

+

38 env=env, 

+

39 requests_message=requests_message, 

+

40 output_options=output_options, 

+

41 processing_options=processing_options, 

+

42 extra_stream_kwargs=extra_stream_kwargs 

+

43 ), 

+

44 # NOTE: `env.stdout` will in fact be `stderr` with `--download` 

+

45 'outfile': env.stdout, 

+

46 'flush': env.stdout_isatty or processing_options.stream 

+

47 } 

+

48 try: 

+

49 if env.is_windows and 'colors' in processing_options.get_prettify(env): 49 ↛ 50line 49 didn't jump to line 50 because the condition on line 49 was never true

+

50 write_stream_with_colors_win(**write_stream_kwargs) 

+

51 else: 

+

52 write_stream(**write_stream_kwargs) 

+

53 except OSError as e: 

+

54 if processing_options.show_traceback and e.errno == errno.EPIPE: 

+

55 # Ignore broken pipes unless --traceback. 

+

56 env.stderr.write('\n') 

+

57 else: 

+

58 raise 

+

59 

+

60 

+

61def write_stream( 

+

62 stream: BaseStream, 

+

63 outfile: Union[IO, TextIO], 

+

64 flush: bool 

+

65): 

+

66 """Write the output stream.""" 

+

67 try: 

+

68 # Writing bytes so we use the buffer interface. 

+

69 buf = outfile.buffer 

+

70 except AttributeError: 

+

71 buf = outfile 

+

72 

+

73 for chunk in stream: 

+

74 buf.write(chunk) 

+

75 if flush: 

+

76 outfile.flush() 

+

77 

+

78 

+

79def write_stream_with_colors_win( 

+

80 stream: 'BaseStream', 

+

81 outfile: TextIO, 

+

82 flush: bool 

+

83): 

+

84 """Like `write`, but colorized chunks are written as text 

+

85 directly to `outfile` to ensure it gets processed by colorama. 

+

86 Applies only to Windows and colorized terminal output. 

+

87 

+

88 """ 

+

89 color = b'\x1b[' 

+

90 encoding = outfile.encoding 

+

91 for chunk in stream: 

+

92 if color in chunk: 

+

93 outfile.write(chunk.decode(encoding)) 

+

94 else: 

+

95 outfile.buffer.write(chunk) 

+

96 if flush: 

+

97 outfile.flush() 

+

98 

+

99 

+

100def write_raw_data( 

+

101 env: Environment, 

+

102 data: Any, 

+

103 *, 

+

104 processing_options: Optional[ProcessingOptions] = None, 

+

105 headers: Optional[HTTPHeadersDict] = None, 

+

106 stream_kwargs: Optional[Dict[str, Any]] = None 

+

107): 

+

108 msg = requests.PreparedRequest() 

+

109 msg.is_body_upload_chunk = True 

+

110 msg.body = data 

+

111 msg.headers = headers or HTTPHeadersDict() 

+

112 msg_output_options = OutputOptions.from_message(msg, body=True, headers=False) 

+

113 return write_message( 

+

114 requests_message=msg, 

+

115 env=env, 

+

116 output_options=msg_output_options, 

+

117 processing_options=processing_options or ProcessingOptions(), 

+

118 extra_stream_kwargs=stream_kwargs 

+

119 ) 

+

120 

+

121 

+

122def build_output_stream_for_message( 

+

123 env: Environment, 

+

124 requests_message: RequestsMessage, 

+

125 output_options: OutputOptions, 

+

126 processing_options: ProcessingOptions, 

+

127 extra_stream_kwargs: Optional[Dict[str, Any]] = None 

+

128): 

+

129 message_type = { 

+

130 RequestsMessageKind.REQUEST: HTTPRequest, 

+

131 RequestsMessageKind.RESPONSE: HTTPResponse, 

+

132 }[output_options.kind] 

+

133 stream_class, stream_kwargs = get_stream_type_and_kwargs( 

+

134 env=env, 

+

135 processing_options=processing_options, 

+

136 message_type=message_type, 

+

137 headers=requests_message.headers 

+

138 ) 

+

139 if extra_stream_kwargs: 

+

140 stream_kwargs.update(extra_stream_kwargs) 

+

141 yield from stream_class( 

+

142 msg=message_type(requests_message), 

+

143 output_options=output_options, 

+

144 **stream_kwargs, 

+

145 ) 

+

146 if (env.stdout_isatty and output_options.body and not output_options.meta 

+

147 and not getattr(requests_message, 'is_body_upload_chunk', False)): 

+

148 # Ensure a blank line after the response body. 

+

149 # For terminal output only. 

+

150 yield MESSAGE_SEPARATOR_BYTES 

+

151 

+

152 

+

153def get_stream_type_and_kwargs( 

+

154 env: Environment, 

+

155 processing_options: ProcessingOptions, 

+

156 message_type: Type[HTTPMessage], 

+

157 headers: HTTPHeadersDict, 

+

158) -> Tuple[Type['BaseStream'], dict]: 

+

159 """Pick the right stream type and kwargs for it based on `env` and `args`. 

+

160 

+

161 """ 

+

162 is_stream = processing_options.stream 

+

163 prettify_groups = processing_options.get_prettify(env) 

+

164 if not is_stream and message_type is HTTPResponse: 

+

165 # If this is a response, then check the headers for determining 

+

166 # auto-streaming. 

+

167 raw_content_type_header = headers.get('Content-Type', None) 

+

168 if raw_content_type_header: 

+

169 content_type_header, _ = parse_content_type_header(raw_content_type_header) 

+

170 is_stream = (content_type_header == 'text/event-stream') 

+

171 

+

172 if not env.stdout_isatty and not prettify_groups: 

+

173 stream_class = RawStream 

+

174 stream_kwargs = { 

+

175 'chunk_size': ( 

+

176 RawStream.CHUNK_SIZE_BY_LINE 

+

177 if is_stream 

+

178 else RawStream.CHUNK_SIZE 

+

179 ) 

+

180 } 

+

181 else: 

+

182 stream_class = EncodedStream 

+

183 stream_kwargs = { 

+

184 'env': env, 

+

185 } 

+

186 if message_type is HTTPResponse: 

+

187 stream_kwargs.update({ 

+

188 'mime_overwrite': processing_options.response_mime, 

+

189 'encoding_overwrite': processing_options.response_charset, 

+

190 }) 

+

191 if prettify_groups: 

+

192 stream_class = PrettyStream if is_stream else BufferedPrettyStream 

+

193 stream_kwargs.update({ 

+

194 'conversion': Conversion(), 

+

195 'formatting': Formatting( 

+

196 env=env, 

+

197 groups=prettify_groups, 

+

198 color_scheme=processing_options.style, 

+

199 explicit_json=processing_options.json, 

+

200 format_options=processing_options.format_options, 

+

201 ) 

+

202 }) 

+

203 

+

204 return stream_class, stream_kwargs 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3___init___py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3___init___py.html new file mode 100644 index 0000000000..ccab24530c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\cli\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\cli\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argparser_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argparser_py.html new file mode 100644 index 0000000000..f349259704 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argparser_py.html @@ -0,0 +1,712 @@ + + + + + Coverage for httpie\cli\argparser.py: 92% + + + + + +
+
+

+ Coverage for httpie\cli\argparser.py: + 92% +

+ +

+ 316 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import errno 

+

3import os 

+

4import re 

+

5import sys 

+

6from argparse import RawDescriptionHelpFormatter 

+

7from textwrap import dedent 

+

8from urllib.parse import urlsplit 

+

9 

+

10from requests.utils import get_netrc_auth 

+

11 

+

12from .argtypes import ( 

+

13 AuthCredentials, SSLCredentials, KeyValueArgType, 

+

14 PARSED_DEFAULT_FORMAT_OPTIONS, 

+

15 parse_auth, 

+

16 parse_format_options, 

+

17) 

+

18from .constants import ( 

+

19 HTTP_GET, HTTP_POST, BASE_OUTPUT_OPTIONS, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, 

+

20 OUTPUT_OPTIONS_DEFAULT_OFFLINE, OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED, 

+

21 OUT_RESP_BODY, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, RequestType, 

+

22 SEPARATOR_CREDENTIALS, 

+

23 SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_GROUP_DATA_ITEMS, URL_SCHEME_RE, 

+

24) 

+

25from .exceptions import ParseError 

+

26from .requestitems import RequestItems 

+

27from ..context import Environment 

+

28from ..plugins.registry import plugin_manager 

+

29from ..utils import ExplicitNullAuth, get_content_type 

+

30 

+

31 

+

32class HTTPieHelpFormatter(RawDescriptionHelpFormatter): 

+

33 """A nicer help formatter. 

+

34 

+

35 Help for arguments can be indented and contain new lines. 

+

36 It will be de-dented and arguments in the help 

+

37 will be separated by a blank line for better readability. 

+

38 

+

39 

+

40 """ 

+

41 

+

42 def __init__(self, max_help_position=6, *args, **kwargs): 

+

43 # A smaller indent for args help. 

+

44 kwargs['max_help_position'] = max_help_position 

+

45 super().__init__(*args, **kwargs) 

+

46 

+

47 def _split_lines(self, text, width): 

+

48 text = dedent(text).strip() + '\n\n' 

+

49 return text.splitlines() 

+

50 

+

51 def add_usage(self, usage, actions, groups, prefix=None): 

+

52 # Only display the positional arguments 

+

53 displayed_actions = [ 

+

54 action 

+

55 for action in actions 

+

56 if not action.option_strings 

+

57 ] 

+

58 

+

59 _, exception, _ = sys.exc_info() 

+

60 if ( 60 ↛ 69line 60 didn't jump to line 69 because the condition on line 60 was never true

+

61 isinstance(exception, argparse.ArgumentError) 

+

62 and len(exception.args) >= 1 

+

63 and isinstance(exception.args[0], argparse.Action) 

+

64 ): 

+

65 # add_usage path is also taken when you pass an invalid option, 

+

66 # e.g --style=invalid. If something like that happens, we want 

+

67 # to include to action that caused to the invalid usage into 

+

68 # the list of actions we are displaying. 

+

69 displayed_actions.insert(0, exception.args[0]) 

+

70 

+

71 super().add_usage( 

+

72 usage, 

+

73 displayed_actions, 

+

74 groups, 

+

75 prefix="usage:\n " 

+

76 ) 

+

77 

+

78 

+

79# TODO: refactor and design type-annotated data structures 

+

80# for raw args + parsed args and keep things immutable. 

+

81class BaseHTTPieArgumentParser(argparse.ArgumentParser): 

+

82 def __init__(self, *args, **kwargs): 

+

83 super().__init__(*args, **kwargs) 

+

84 self.env = None 

+

85 self.args = None 

+

86 self.has_stdin_data = False 

+

87 self.has_input_data = False 

+

88 

+

89 # noinspection PyMethodOverriding 

+

90 def parse_args( 

+

91 self, 

+

92 env: Environment, 

+

93 args=None, 

+

94 namespace=None 

+

95 ) -> argparse.Namespace: 

+

96 self.env = env 

+

97 self.args, no_options = self.parse_known_args(args, namespace) 

+

98 if self.args.debug: 

+

99 self.args.traceback = True 

+

100 self.has_stdin_data = ( 

+

101 self.env.stdin 

+

102 and not getattr(self.args, 'ignore_stdin', False) 

+

103 and not self.env.stdin_isatty 

+

104 ) 

+

105 self.has_input_data = self.has_stdin_data or getattr(self.args, 'raw', None) is not None 

+

106 return self.args 

+

107 

+

108 # noinspection PyShadowingBuiltins 

+

109 def _print_message(self, message, file=None): 

+

110 # Sneak in our stderr/stdout. 

+

111 if hasattr(self, 'root'): 

+

112 env = self.root.env 

+

113 else: 

+

114 env = self.env 

+

115 

+

116 if env is not None: 116 ↛ 123line 116 didn't jump to line 123 because the condition on line 116 was always true

+

117 file = { 

+

118 sys.stdout: env.stdout, 

+

119 sys.stderr: env.stderr, 

+

120 None: env.stderr 

+

121 }.get(file, file) 

+

122 

+

123 if not hasattr(file, 'buffer') and isinstance(message, str): 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true

+

124 message = message.encode(env.stdout_encoding) 

+

125 super()._print_message(message, file) 

+

126 

+

127 

+

128class HTTPieManagerArgumentParser(BaseHTTPieArgumentParser): 

+

129 def parse_known_args(self, args=None, namespace=None): 

+

130 try: 

+

131 return super().parse_known_args(args, namespace) 

+

132 except SystemExit as exc: 

+

133 if not hasattr(self, 'root') and exc.code == 2: # Argument Parser Error 

+

134 raise argparse.ArgumentError(None, None) 

+

135 raise 

+

136 

+

137 

+

138class HTTPieArgumentParser(BaseHTTPieArgumentParser): 

+

139 """Adds additional logic to `argparse.ArgumentParser`. 

+

140 

+

141 Handles all input (CLI args, file args, stdin), applies defaults, 

+

142 and performs extra validation. 

+

143 

+

144 """ 

+

145 

+

146 def __init__(self, *args, formatter_class=HTTPieHelpFormatter, **kwargs): 

+

147 kwargs.setdefault('add_help', False) 

+

148 super().__init__(*args, formatter_class=formatter_class, **kwargs) 

+

149 

+

150 # noinspection PyMethodOverriding 

+

151 def parse_args( 

+

152 self, 

+

153 env: Environment, 

+

154 args=None, 

+

155 namespace=None 

+

156 ) -> argparse.Namespace: 

+

157 self.env = env 

+

158 self.env.args = namespace = namespace or argparse.Namespace() 

+

159 self.args, no_options = super().parse_known_args(args, namespace) 

+

160 if self.args.debug: 

+

161 self.args.traceback = True 

+

162 self.has_stdin_data = ( 

+

163 self.env.stdin 

+

164 and not self.args.ignore_stdin 

+

165 and not self.env.stdin_isatty 

+

166 ) 

+

167 self.has_input_data = self.has_stdin_data or self.args.raw is not None 

+

168 # Arguments processing and environment setup. 

+

169 self._apply_no_options(no_options) 

+

170 self._process_request_type() 

+

171 self._process_download_options() 

+

172 self._setup_standard_streams() 

+

173 self._process_output_options() 

+

174 self._process_pretty_options() 

+

175 self._process_format_options() 

+

176 self._guess_method() 

+

177 self._parse_items() 

+

178 self._process_url() 

+

179 self._process_auth() 

+

180 self._process_ssl_cert() 

+

181 

+

182 if self.args.raw is not None: 

+

183 self._body_from_input(self.args.raw) 

+

184 elif self.has_stdin_data: 

+

185 self._body_from_file(self.env.stdin) 

+

186 

+

187 if self.args.compress: 

+

188 # TODO: allow --compress with --chunked / --multipart 

+

189 if self.args.chunked: 

+

190 self.error('cannot combine --compress and --chunked') 

+

191 if self.args.multipart: 

+

192 self.error('cannot combine --compress and --multipart') 

+

193 

+

194 return self.args 

+

195 

+

196 def _process_request_type(self): 

+

197 request_type = self.args.request_type 

+

198 self.args.json = request_type is RequestType.JSON 

+

199 self.args.multipart = request_type is RequestType.MULTIPART 

+

200 self.args.form = request_type in { 

+

201 RequestType.FORM, 

+

202 RequestType.MULTIPART, 

+

203 } 

+

204 

+

205 def _process_url(self): 

+

206 if self.args.url.startswith('://'): 

+

207 # Paste URL & add space shortcut: `http ://pie.dev` → `http://pie.dev` 

+

208 self.args.url = self.args.url[3:] 

+

209 if not URL_SCHEME_RE.match(self.args.url): 

+

210 if os.path.basename(self.env.program_name) == 'https': 

+

211 scheme = 'https://' 

+

212 else: 

+

213 scheme = self.args.default_scheme + '://' 

+

214 

+

215 # See if we're using curl style shorthand for localhost (:3000/foo) 

+

216 shorthand = re.match(r'^:(?!:)(\d*)(/?.*)$', self.args.url) 

+

217 if shorthand: 

+

218 port = shorthand.group(1) 

+

219 rest = shorthand.group(2) 

+

220 self.args.url = scheme + 'localhost' 

+

221 if port: 

+

222 self.args.url += ':' + port 

+

223 self.args.url += rest 

+

224 else: 

+

225 self.args.url = scheme + self.args.url 

+

226 

+

227 def _setup_standard_streams(self): 

+

228 """ 

+

229 Modify `env.stdout` and `env.stdout_isatty` based on args, if needed. 

+

230 

+

231 """ 

+

232 

+

233 self.args.output_file_specified = bool(self.args.output_file) 

+

234 if self.args.download: 

+

235 # FIXME: Come up with a cleaner solution. 

+

236 if not self.args.output_file and not self.env.stdout_isatty: 

+

237 # Use stdout as the download output file. 

+

238 self.args.output_file = self.env.stdout 

+

239 # With `--download`, we write everything that would normally go to 

+

240 # `stdout` to `stderr` instead. Let's replace the stream so that 

+

241 # we don't have to use many `if`s throughout the codebase. 

+

242 # The response body will be treated separately. 

+

243 self.env.stdout = self.env.stderr 

+

244 self.env.stdout_isatty = self.env.stderr_isatty 

+

245 

+

246 elif self.args.output_file: 

+

247 # When not `--download`ing, then `--output` simply replaces 

+

248 # `stdout`. The file is opened for appending, which isn't what 

+

249 # we want in this case. 

+

250 self.args.output_file.seek(0) 

+

251 try: 

+

252 self.args.output_file.truncate() 

+

253 except OSError as e: 

+

254 if e.errno == errno.EINVAL: 

+

255 # E.g. /dev/null on Linux. 

+

256 pass 

+

257 else: 

+

258 raise 

+

259 self.env.stdout = self.args.output_file 

+

260 self.env.stdout_isatty = False 

+

261 

+

262 if self.args.quiet: 

+

263 self.env.quiet = self.args.quiet 

+

264 self.env.stderr = self.env.devnull 

+

265 if not (self.args.output_file_specified and not self.args.download): 

+

266 self.env.stdout = self.env.devnull 

+

267 self.env.apply_warnings_filter() 

+

268 

+

269 def _process_ssl_cert(self): 

+

270 from httpie.ssl_ import _is_key_file_encrypted 

+

271 

+

272 if self.args.cert_key_pass is None: 

+

273 self.args.cert_key_pass = SSLCredentials(None) 

+

274 

+

275 if ( 

+

276 self.args.cert_key is not None 

+

277 and self.args.cert_key_pass.value is None 

+

278 and _is_key_file_encrypted(self.args.cert_key) 

+

279 ): 

+

280 self.args.cert_key_pass.prompt_password(self.args.cert_key) 

+

281 

+

282 def _process_auth(self): 

+

283 # TODO: refactor & simplify this method. 

+

284 self.args.auth_plugin = None 

+

285 default_auth_plugin = plugin_manager.get_auth_plugins()[0] 

+

286 auth_type_set = self.args.auth_type is not None 

+

287 url = urlsplit(self.args.url) 

+

288 

+

289 if self.args.auth is None and not auth_type_set: 

+

290 if url.username is not None: 

+

291 # Handle http://username:password@hostname/ 

+

292 username = url.username 

+

293 password = url.password or '' 

+

294 self.args.auth = AuthCredentials( 

+

295 key=username, 

+

296 value=password, 

+

297 sep=SEPARATOR_CREDENTIALS, 

+

298 orig=SEPARATOR_CREDENTIALS.join([username, password]) 

+

299 ) 

+

300 

+

301 if self.args.auth is not None or auth_type_set: 

+

302 if not self.args.auth_type: 

+

303 self.args.auth_type = default_auth_plugin.auth_type 

+

304 plugin = plugin_manager.get_auth_plugin(self.args.auth_type)() 

+

305 

+

306 if (not self.args.ignore_netrc 

+

307 and self.args.auth is None 

+

308 and plugin.netrc_parse): 

+

309 # Only host needed, so it’s OK URL not finalized. 

+

310 netrc_credentials = get_netrc_auth(self.args.url) 

+

311 if netrc_credentials: 

+

312 self.args.auth = AuthCredentials( 

+

313 key=netrc_credentials[0], 

+

314 value=netrc_credentials[1], 

+

315 sep=SEPARATOR_CREDENTIALS, 

+

316 orig=SEPARATOR_CREDENTIALS.join(netrc_credentials) 

+

317 ) 

+

318 

+

319 if plugin.auth_require and self.args.auth is None: 

+

320 self.error('--auth required') 

+

321 

+

322 plugin.raw_auth = self.args.auth 

+

323 self.args.auth_plugin = plugin 

+

324 already_parsed = isinstance(self.args.auth, AuthCredentials) 

+

325 

+

326 if self.args.auth is None or not plugin.auth_parse: 

+

327 self.args.auth = plugin.get_auth() 

+

328 else: 

+

329 if already_parsed: 

+

330 # from the URL 

+

331 credentials = self.args.auth 

+

332 else: 

+

333 credentials = parse_auth(self.args.auth) 

+

334 

+

335 if (not credentials.has_password() 

+

336 and plugin.prompt_password): 

+

337 if self.args.ignore_stdin: 

+

338 # Non-tty stdin read by now 

+

339 self.error( 

+

340 'Unable to prompt for passwords because' 

+

341 ' --ignore-stdin is set.' 

+

342 ) 

+

343 credentials.prompt_password(url.netloc) 

+

344 

+

345 if (credentials.key and credentials.value): 

+

346 plugin.raw_auth = credentials.key + ":" + credentials.value 

+

347 

+

348 self.args.auth = plugin.get_auth( 

+

349 username=credentials.key, 

+

350 password=credentials.value, 

+

351 ) 

+

352 if not self.args.auth and self.args.ignore_netrc: 

+

353 # Set a no-op auth to force requests to ignore .netrc 

+

354 # <https://github.com/psf/requests/issues/2773#issuecomment-174312831> 

+

355 self.args.auth = ExplicitNullAuth() 

+

356 

+

357 def _apply_no_options(self, no_options): 

+

358 """For every `--no-OPTION` in `no_options`, set `args.OPTION` to 

+

359 its default value. This allows for un-setting of options, e.g., 

+

360 specified in config. 

+

361 

+

362 """ 

+

363 invalid = [] 

+

364 

+

365 for option in no_options: 

+

366 if not option.startswith('--no-'): 366 ↛ 367line 366 didn't jump to line 367 because the condition on line 366 was never true

+

367 invalid.append(option) 

+

368 continue 

+

369 

+

370 # --no-option => --option 

+

371 inverted = '--' + option[5:] 

+

372 for action in self._actions: 

+

373 if inverted in action.option_strings: 

+

374 setattr(self.args, action.dest, action.default) 

+

375 break 

+

376 else: 

+

377 invalid.append(option) 

+

378 

+

379 if invalid: 

+

380 self.error(f'unrecognized arguments: {" ".join(invalid)}') 

+

381 

+

382 def _body_from_file(self, fd): 

+

383 """Read the data from a file-like object. 

+

384 

+

385 Bytes are always read. 

+

386 

+

387 """ 

+

388 self._ensure_one_data_source(self.args.data, self.args.files) 

+

389 self.args.data = getattr(fd, 'buffer', fd) 

+

390 

+

391 def _body_from_input(self, data): 

+

392 """Read the data from the CLI. 

+

393 

+

394 """ 

+

395 self._ensure_one_data_source(self.has_stdin_data, self.args.data, 

+

396 self.args.files) 

+

397 self.args.data = data.encode() 

+

398 

+

399 def _ensure_one_data_source(self, *other_sources): 

+

400 """There can only be one source of input request data. 

+

401 

+

402 """ 

+

403 if any(other_sources): 

+

404 self.error('Request body (from stdin, --raw or a file) and request ' 

+

405 'data (key=value) cannot be mixed. Pass ' 

+

406 '--ignore-stdin to let key/value take priority. ' 

+

407 'See https://httpie.io/docs#scripting for details.') 

+

408 

+

409 def _guess_method(self): 

+

410 """Set `args.method` if not specified to either POST or GET 

+

411 based on whether the request has data or not. 

+

412 

+

413 """ 

+

414 if self.args.method is None: 

+

415 # Invoked as `http URL'. 

+

416 assert not self.args.request_items 

+

417 if self.has_input_data: 

+

418 self.args.method = HTTP_POST 

+

419 else: 

+

420 self.args.method = HTTP_GET 

+

421 

+

422 # FIXME: False positive, e.g., "localhost" matches but is a valid URL. 

+

423 elif not re.match('^[a-zA-Z]+$', self.args.method): 

+

424 # Invoked as `http URL item+'. The URL is now in `args.method` 

+

425 # and the first ITEM is now incorrectly in `args.url`. 

+

426 try: 

+

427 # Parse the URL as an ITEM and store it as the first ITEM arg. 

+

428 self.args.request_items.insert(0, KeyValueArgType( 

+

429 *SEPARATOR_GROUP_ALL_ITEMS).__call__(self.args.url)) 

+

430 

+

431 except argparse.ArgumentTypeError as e: 

+

432 if self.args.traceback: 432 ↛ 433line 432 didn't jump to line 433 because the condition on line 432 was never true

+

433 raise 

+

434 self.error(e.args[0]) 

+

435 

+

436 else: 

+

437 # Set the URL correctly 

+

438 self.args.url = self.args.method 

+

439 # Infer the method 

+

440 has_data = ( 

+

441 self.has_input_data 

+

442 or any( 

+

443 item.sep in SEPARATOR_GROUP_DATA_ITEMS 

+

444 for item in self.args.request_items) 

+

445 ) 

+

446 self.args.method = HTTP_POST if has_data else HTTP_GET 

+

447 

+

448 def _parse_items(self): 

+

449 """ 

+

450 Parse `args.request_items` into `args.headers`, `args.data`, 

+

451 `args.params`, and `args.files`. 

+

452 

+

453 """ 

+

454 try: 

+

455 request_items = RequestItems.from_args( 

+

456 request_item_args=self.args.request_items, 

+

457 request_type=self.args.request_type, 

+

458 ) 

+

459 except ParseError as e: 

+

460 if self.args.traceback: 460 ↛ 462line 460 didn't jump to line 462 because the condition on line 460 was always true

+

461 raise 

+

462 self.error(e.args[0]) 

+

463 else: 

+

464 self.args.headers = request_items.headers 

+

465 self.args.data = request_items.data 

+

466 self.args.files = request_items.files 

+

467 self.args.params = request_items.params 

+

468 self.args.multipart_data = request_items.multipart_data 

+

469 

+

470 if self.args.files and not self.args.form: 

+

471 # `http url @/path/to/file` 

+

472 request_file = None 

+

473 for key, file in self.args.files.items(): 

+

474 if key != '': 

+

475 self.error( 

+

476 'Invalid file fields (perhaps you meant --form?):' 

+

477 f' {",".join(self.args.files.keys())}') 

+

478 if request_file is not None: 

+

479 self.error("Can't read request from multiple files") 

+

480 request_file = file 

+

481 

+

482 fn, fd, ct = request_file 

+

483 self.args.files = {} 

+

484 

+

485 self._body_from_file(fd) 

+

486 

+

487 if 'Content-Type' not in self.args.headers: 

+

488 content_type = get_content_type(fn) 

+

489 if content_type: 489 ↛ exitline 489 didn't return from function '_parse_items' because the condition on line 489 was always true

+

490 self.args.headers['Content-Type'] = content_type 

+

491 

+

492 def _process_output_options(self): 

+

493 """Apply defaults to output options, or validate the provided ones. 

+

494 

+

495 The default output options are stdout-type-sensitive. 

+

496 

+

497 """ 

+

498 

+

499 def check_options(value, option): 

+

500 unknown = set(value) - OUTPUT_OPTIONS 

+

501 if unknown: 501 ↛ 502line 501 didn't jump to line 502 because the condition on line 501 was never true

+

502 self.error(f'Unknown output options: {option}={",".join(unknown)}') 

+

503 

+

504 if self.args.verbose: 

+

505 self.args.all = True 

+

506 

+

507 if self.args.output_options is None: 

+

508 if self.args.verbose >= 2: 

+

509 self.args.output_options = ''.join(OUTPUT_OPTIONS) 

+

510 elif self.args.verbose == 1: 

+

511 self.args.output_options = ''.join(BASE_OUTPUT_OPTIONS) 

+

512 elif self.args.offline: 

+

513 self.args.output_options = OUTPUT_OPTIONS_DEFAULT_OFFLINE 

+

514 elif not self.env.stdout_isatty: 

+

515 self.args.output_options = OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED 

+

516 else: 

+

517 self.args.output_options = OUTPUT_OPTIONS_DEFAULT 

+

518 

+

519 if self.args.output_options_history is None: 519 ↛ 522line 519 didn't jump to line 522 because the condition on line 519 was always true

+

520 self.args.output_options_history = self.args.output_options 

+

521 

+

522 check_options(self.args.output_options, '--print') 

+

523 check_options(self.args.output_options_history, '--history-print') 

+

524 

+

525 if self.args.download and OUT_RESP_BODY in self.args.output_options: 

+

526 # Response body is always downloaded with --download and it goes 

+

527 # through a different routine, so we remove it. 

+

528 self.args.output_options = str( 

+

529 set(self.args.output_options) - set(OUT_RESP_BODY)) 

+

530 

+

531 def _process_pretty_options(self): 

+

532 if self.args.prettify == PRETTY_STDOUT_TTY_ONLY: 

+

533 self.args.prettify = PRETTY_MAP[ 

+

534 'all' if self.env.stdout_isatty else 'none'] 

+

535 elif (self.args.prettify and self.env.is_windows 

+

536 and self.args.output_file): 

+

537 self.error('Only terminal output can be colorized on Windows.') 

+

538 else: 

+

539 # noinspection PyTypeChecker 

+

540 self.args.prettify = PRETTY_MAP[self.args.prettify] 

+

541 

+

542 def _process_download_options(self): 

+

543 if self.args.offline: 

+

544 self.args.download = False 

+

545 self.args.download_resume = False 

+

546 return 

+

547 if not self.args.download: 

+

548 if self.args.download_resume: 548 ↛ 549line 548 didn't jump to line 549 because the condition on line 548 was never true

+

549 self.error('--continue only works with --download') 

+

550 if self.args.download_resume and not ( 550 ↛ 552line 550 didn't jump to line 552 because the condition on line 550 was never true

+

551 self.args.download and self.args.output_file): 

+

552 self.error('--continue requires --output to be specified') 

+

553 

+

554 def _process_format_options(self): 

+

555 format_options = self.args.format_options or [] 

+

556 parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS 

+

557 for options_group in format_options: 

+

558 parsed_options = parse_format_options(options_group, defaults=parsed_options) 

+

559 self.args.format_options = parsed_options 

+

560 

+

561 def print_manual(self): 

+

562 from httpie.output.ui import man_pages 

+

563 

+

564 if man_pages.is_available(self.env.program_name): 

+

565 man_pages.display_for(self.env, self.env.program_name) 

+

566 return None 

+

567 

+

568 text = self.format_help() 

+

569 with self.env.rich_console.pager(): 

+

570 self.env.rich_console.print( 

+

571 text, 

+

572 highlight=False 

+

573 ) 

+

574 

+

575 def print_usage(self, file): 

+

576 from rich.text import Text 

+

577 from httpie.output.ui import rich_help 

+

578 

+

579 whitelist = set() 

+

580 _, exception, _ = sys.exc_info() 

+

581 if ( 

+

582 isinstance(exception, argparse.ArgumentError) 

+

583 and len(exception.args) >= 1 

+

584 and isinstance(exception.args[0], argparse.Action) 

+

585 and exception.args[0].option_strings 

+

586 ): 

+

587 # add_usage path is also taken when you pass an invalid option, 

+

588 # e.g --style=invalid. If something like that happens, we want 

+

589 # to include to action that caused to the invalid usage into 

+

590 # the list of actions we are displaying. 

+

591 whitelist.add(exception.args[0].option_strings[0]) 

+

592 

+

593 usage_text = Text('usage', style='bold') 

+

594 usage_text.append(':\n ') 

+

595 usage_text.append(rich_help.to_usage(self.spec, whitelist=whitelist)) 

+

596 self.env.rich_error_console.print(usage_text) 

+

597 

+

598 def error(self, message): 

+

599 """Prints a usage message incorporating the message to stderr and 

+

600 exits.""" 

+

601 self.print_usage(sys.stderr) 

+

602 self.env.rich_error_console.print( 

+

603 dedent( 

+

604 f''' 

+

605 [bold]error[/bold]: 

+

606 {message} 

+

607 

+

608 [bold]for more information[/bold]: 

+

609 run '{self.prog} --help' or visit https://httpie.io/docs/cli 

+

610 '''.rstrip() 

+

611 ) 

+

612 ) 

+

613 self.exit(2) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argtypes_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argtypes_py.html new file mode 100644 index 0000000000..5f2e6e783d --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_argtypes_py.html @@ -0,0 +1,374 @@ + + + + + Coverage for httpie\cli\argtypes.py: 95% + + + + + +
+
+

+ Coverage for httpie\cli\argtypes.py: + 95% +

+ +

+ 136 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import getpass 

+

3import os 

+

4import sys 

+

5from copy import deepcopy 

+

6from typing import List, Optional, Union 

+

7 

+

8from .constants import DEFAULT_FORMAT_OPTIONS, SEPARATOR_CREDENTIALS 

+

9from ..sessions import VALID_SESSION_NAME_PATTERN 

+

10 

+

11 

+

12class KeyValueArg: 

+

13 """Base key-value pair parsed from CLI.""" 

+

14 

+

15 def __init__(self, key: str, value: Optional[str], sep: str, orig: str): 

+

16 self.key = key 

+

17 self.value = value 

+

18 self.sep = sep 

+

19 self.orig = orig 

+

20 

+

21 def __eq__(self, other: 'KeyValueArg'): 

+

22 return self.__dict__ == other.__dict__ 

+

23 

+

24 def __repr__(self): 

+

25 return repr(self.__dict__) 

+

26 

+

27 

+

28class SessionNameValidator: 

+

29 

+

30 def __init__(self, error_message: str): 

+

31 self.error_message = error_message 

+

32 

+

33 def __call__(self, value: str) -> str: 

+

34 # Session name can be a path or just a name. 

+

35 if (os.path.sep not in value 35 ↛ 37line 35 didn't jump to line 37 because the condition on line 35 was never true

+

36 and not VALID_SESSION_NAME_PATTERN.search(value)): 

+

37 raise argparse.ArgumentError(None, self.error_message) 

+

38 return value 

+

39 

+

40 

+

41class Escaped(str): 

+

42 """Represents an escaped character.""" 

+

43 

+

44 def __repr__(self): 

+

45 return f"Escaped({repr(str(self))})" 

+

46 

+

47 

+

48class KeyValueArgType: 

+

49 """A key-value pair argument type used with `argparse`. 

+

50 

+

51 Parses a key-value arg and constructs a `KeyValueArg` instance. 

+

52 Used for headers, form data, and other key-value pair types. 

+

53 

+

54 """ 

+

55 

+

56 key_value_class = KeyValueArg 

+

57 

+

58 def __init__(self, *separators: str): 

+

59 self.separators = separators 

+

60 self.special_characters = set() 

+

61 for separator in separators: 

+

62 self.special_characters.update(separator) 

+

63 

+

64 def __call__(self, s: str) -> KeyValueArg: 

+

65 """Parse raw string arg and return `self.key_value_class` instance. 

+

66 

+

67 The best of `self.separators` is determined (first found, longest). 

+

68 Back slash escaped characters aren't considered as separators 

+

69 (or parts thereof). Literal back slash characters have to be escaped 

+

70 as well (r'\\'). 

+

71 

+

72 """ 

+

73 tokens = self.tokenize(s) 

+

74 

+

75 # Sorting by length ensures that the longest one will be 

+

76 # chosen as it will overwrite any shorter ones starting 

+

77 # at the same position in the `found` dictionary. 

+

78 separators = sorted(self.separators, key=len) 

+

79 

+

80 for i, token in enumerate(tokens): 

+

81 

+

82 if isinstance(token, Escaped): 

+

83 continue 

+

84 

+

85 found = {} 

+

86 for sep in separators: 

+

87 pos = token.find(sep) 

+

88 if pos != -1: 

+

89 found[pos] = sep 

+

90 

+

91 if found: 

+

92 # Starting first, longest separator found. 

+

93 sep = found[min(found.keys())] 

+

94 

+

95 key, value = token.split(sep, 1) 

+

96 

+

97 # Any preceding tokens are part of the key. 

+

98 key = ''.join(tokens[:i]) + key 

+

99 

+

100 # Any following tokens are part of the value. 

+

101 value += ''.join(tokens[i + 1:]) 

+

102 

+

103 break 

+

104 

+

105 else: 

+

106 raise argparse.ArgumentTypeError(f'{s!r} is not a valid value') 

+

107 

+

108 return self.key_value_class(key=key, value=value, sep=sep, orig=s) 

+

109 

+

110 def tokenize(self, s: str) -> List[Union[str, Escaped]]: 

+

111 r"""Tokenize the raw arg string 

+

112 

+

113 There are only two token types - strings and escaped characters: 

+

114 

+

115 >>> KeyValueArgType('=').tokenize(r'foo\=bar\\baz') 

+

116 ['foo', Escaped('='), 'bar\\\\baz'] 

+

117 

+

118 """ 

+

119 tokens = [''] 

+

120 characters = iter(s) 

+

121 for char in characters: 

+

122 if char == '\\': 

+

123 char = next(characters, '') 

+

124 if char not in self.special_characters: 

+

125 tokens[-1] += '\\' + char 

+

126 else: 

+

127 tokens.extend([Escaped(char), '']) 

+

128 else: 

+

129 tokens[-1] += char 

+

130 return tokens 

+

131 

+

132 

+

133class PromptMixin: 

+

134 def _prompt_password(self, prompt: str) -> str: 

+

135 prompt_text = f'http: {prompt}: ' 

+

136 try: 

+

137 return self._getpass(prompt_text) 

+

138 except (EOFError, KeyboardInterrupt): 

+

139 sys.stderr.write('\n') 

+

140 sys.exit(0) 

+

141 

+

142 @staticmethod 

+

143 def _getpass(prompt): 

+

144 # To allow easy mocking. 

+

145 return getpass.getpass(str(prompt)) 

+

146 

+

147 

+

148class SSLCredentials(PromptMixin): 

+

149 """Represents the passphrase for the certificate's key.""" 

+

150 

+

151 def __init__(self, value: Optional[str]) -> None: 

+

152 self.value = value 

+

153 

+

154 def prompt_password(self, key_file: str) -> None: 

+

155 self.value = self._prompt_password(f'passphrase for {key_file}') 

+

156 

+

157 

+

158class AuthCredentials(KeyValueArg, PromptMixin): 

+

159 """Represents parsed credentials.""" 

+

160 

+

161 def has_password(self) -> bool: 

+

162 return self.value is not None 

+

163 

+

164 def prompt_password(self, host: str) -> None: 

+

165 self.value = self._prompt_password(f'password for {self.key}@{host}:') 

+

166 

+

167 

+

168class AuthCredentialsArgType(KeyValueArgType): 

+

169 """A key-value arg type that parses credentials.""" 

+

170 

+

171 key_value_class = AuthCredentials 

+

172 

+

173 def __call__(self, s): 

+

174 """Parse credentials from `s`. 

+

175 

+

176 ("username" or "username:password"). 

+

177 

+

178 """ 

+

179 try: 

+

180 return super().__call__(s) 

+

181 except argparse.ArgumentTypeError: 

+

182 # No password provided, will prompt for it later. 

+

183 return self.key_value_class( 

+

184 key=s, 

+

185 value=None, 

+

186 sep=SEPARATOR_CREDENTIALS, 

+

187 orig=s 

+

188 ) 

+

189 

+

190 

+

191parse_auth = AuthCredentialsArgType(SEPARATOR_CREDENTIALS) 

+

192 

+

193 

+

194def readable_file_arg(filename): 

+

195 try: 

+

196 with open(filename, 'rb'): 

+

197 return filename 

+

198 except OSError as ex: 

+

199 raise argparse.ArgumentTypeError(f'{ex.filename}: {ex.strerror}') 

+

200 

+

201 

+

202def parse_format_options(s: str, defaults: Optional[dict]) -> dict: 

+

203 """ 

+

204 Parse `s` and update `defaults` with the parsed values. 

+

205 

+

206 >>> parse_format_options( 

+

207 ... defaults={'json': {'indent': 4, 'sort_keys': True}}, 

+

208 ... s='json.indent:2,json.sort_keys:False', 

+

209 ... ) 

+

210 {'json': {'indent': 2, 'sort_keys': False}} 

+

211 

+

212 """ 

+

213 value_map = { 

+

214 'true': True, 

+

215 'false': False, 

+

216 } 

+

217 options = deepcopy(defaults or {}) 

+

218 for option in s.split(','): 

+

219 try: 

+

220 path, value = option.lower().split(':') 

+

221 section, key = path.split('.') 

+

222 except ValueError: 

+

223 raise argparse.ArgumentTypeError(f'invalid option {option!r}') 

+

224 

+

225 if value in value_map: 

+

226 parsed_value = value_map[value] 

+

227 else: 

+

228 if value.isnumeric(): 

+

229 parsed_value = int(value) 

+

230 else: 

+

231 parsed_value = value 

+

232 

+

233 if defaults is None: 

+

234 options.setdefault(section, {}) 

+

235 else: 

+

236 try: 

+

237 default_value = defaults[section][key] 

+

238 except KeyError: 

+

239 raise argparse.ArgumentTypeError( 

+

240 f'invalid key {path!r}') 

+

241 

+

242 default_type, parsed_type = type(default_value), type(parsed_value) 

+

243 if parsed_type is not default_type: 

+

244 raise argparse.ArgumentTypeError( 

+

245 'invalid value' 

+

246 f' {value!r} in {option!r}' 

+

247 f' (expected {default_type.__name__}' 

+

248 f' got {parsed_type.__name__})' 

+

249 ) 

+

250 

+

251 options[section][key] = parsed_value 

+

252 

+

253 return options 

+

254 

+

255 

+

256PARSED_DEFAULT_FORMAT_OPTIONS = parse_format_options( 

+

257 s=','.join(DEFAULT_FORMAT_OPTIONS), 

+

258 defaults=None, 

+

259) 

+

260 

+

261 

+

262def response_charset_type(encoding: str) -> str: 

+

263 try: 

+

264 ''.encode(encoding) 

+

265 except LookupError: 

+

266 raise argparse.ArgumentTypeError( 

+

267 f'{encoding!r} is not a supported encoding') 

+

268 return encoding 

+

269 

+

270 

+

271def response_mime_type(mime_type: str) -> str: 

+

272 if mime_type.count('/') != 1: 

+

273 raise argparse.ArgumentTypeError( 

+

274 f'{mime_type!r} doesn’t look like a mime type; use type/subtype') 

+

275 return mime_type 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_constants_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_constants_py.html new file mode 100644 index 0000000000..e3b88a900c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_constants_py.html @@ -0,0 +1,233 @@ + + + + + Coverage for httpie\cli\constants.py: 100% + + + + + +
+
+

+ Coverage for httpie\cli\constants.py: + 100% +

+ +

+ 46 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1"""Parsing and processing of CLI input (args, auth credentials, files, stdin). 

+

2 

+

3""" 

+

4import enum 

+

5import re 

+

6 

+

7 

+

8URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE) 

+

9 

+

10HTTP_POST = 'POST' 

+

11HTTP_GET = 'GET' 

+

12HTTP_OPTIONS = 'OPTIONS' 

+

13 

+

14# Various separators used in args 

+

15SEPARATOR_HEADER = ':' 

+

16SEPARATOR_HEADER_EMPTY = ';' 

+

17SEPARATOR_CREDENTIALS = ':' 

+

18SEPARATOR_PROXY = ':' 

+

19SEPARATOR_HEADER_EMBED = ':@' 

+

20SEPARATOR_DATA_STRING = '=' 

+

21SEPARATOR_DATA_RAW_JSON = ':=' 

+

22SEPARATOR_FILE_UPLOAD = '@' 

+

23SEPARATOR_FILE_UPLOAD_TYPE = ';type=' # in already parsed file upload path only 

+

24SEPARATOR_DATA_EMBED_FILE_CONTENTS = '=@' 

+

25SEPARATOR_DATA_EMBED_RAW_JSON_FILE = ':=@' 

+

26SEPARATOR_QUERY_PARAM = '==' 

+

27SEPARATOR_QUERY_EMBED_FILE = '==@' 

+

28 

+

29# Separators that become request data 

+

30SEPARATOR_GROUP_DATA_ITEMS = frozenset({ 

+

31 SEPARATOR_DATA_STRING, 

+

32 SEPARATOR_DATA_RAW_JSON, 

+

33 SEPARATOR_FILE_UPLOAD, 

+

34 SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

35 SEPARATOR_DATA_EMBED_RAW_JSON_FILE 

+

36}) 

+

37 

+

38SEPARATORS_GROUP_MULTIPART = frozenset({ 

+

39 SEPARATOR_DATA_STRING, 

+

40 SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

41 SEPARATOR_FILE_UPLOAD, 

+

42}) 

+

43 

+

44# Separators for items whose value is a filename to be embedded 

+

45SEPARATOR_GROUP_DATA_EMBED_ITEMS = frozenset({ 

+

46 SEPARATOR_HEADER_EMBED, 

+

47 SEPARATOR_QUERY_EMBED_FILE, 

+

48 SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

49 SEPARATOR_DATA_EMBED_RAW_JSON_FILE, 

+

50}) 

+

51 

+

52# Separators for nested JSON items 

+

53SEPARATOR_GROUP_NESTED_JSON_ITEMS = frozenset([ 

+

54 SEPARATOR_DATA_STRING, 

+

55 SEPARATOR_DATA_RAW_JSON, 

+

56 SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

57 SEPARATOR_DATA_EMBED_RAW_JSON_FILE, 

+

58]) 

+

59 

+

60# Separators allowed in ITEM arguments 

+

61SEPARATOR_GROUP_ALL_ITEMS = frozenset({ 

+

62 SEPARATOR_HEADER, 

+

63 SEPARATOR_HEADER_EMPTY, 

+

64 SEPARATOR_HEADER_EMBED, 

+

65 SEPARATOR_QUERY_PARAM, 

+

66 SEPARATOR_QUERY_EMBED_FILE, 

+

67 SEPARATOR_DATA_STRING, 

+

68 SEPARATOR_DATA_RAW_JSON, 

+

69 SEPARATOR_FILE_UPLOAD, 

+

70 SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

71 SEPARATOR_DATA_EMBED_RAW_JSON_FILE, 

+

72}) 

+

73 

+

74# Output options 

+

75OUT_REQ_HEAD = 'H' 

+

76OUT_REQ_BODY = 'B' 

+

77OUT_RESP_HEAD = 'h' 

+

78OUT_RESP_BODY = 'b' 

+

79OUT_RESP_META = 'm' 

+

80 

+

81BASE_OUTPUT_OPTIONS = frozenset({ 

+

82 OUT_REQ_HEAD, 

+

83 OUT_REQ_BODY, 

+

84 OUT_RESP_HEAD, 

+

85 OUT_RESP_BODY, 

+

86}) 

+

87 

+

88OUTPUT_OPTIONS = frozenset({ 

+

89 *BASE_OUTPUT_OPTIONS, 

+

90 OUT_RESP_META, 

+

91}) 

+

92 

+

93# Pretty 

+

94 

+

95 

+

96class PrettyOptions(enum.Enum): 

+

97 STDOUT_TTY_ONLY = enum.auto() 

+

98 

+

99 

+

100PRETTY_MAP = { 

+

101 'all': ['format', 'colors'], 

+

102 'colors': ['colors'], 

+

103 'format': ['format'], 

+

104 'none': [] 

+

105} 

+

106PRETTY_STDOUT_TTY_ONLY = PrettyOptions.STDOUT_TTY_ONLY 

+

107 

+

108 

+

109DEFAULT_FORMAT_OPTIONS = [ 

+

110 'headers.sort:true', 

+

111 'json.format:true', 

+

112 'json.indent:4', 

+

113 'json.sort_keys:true', 

+

114 'xml.format:true', 

+

115 'xml.indent:2', 

+

116] 

+

117SORTED_FORMAT_OPTIONS = [ 

+

118 'headers.sort:true', 

+

119 'json.sort_keys:true', 

+

120] 

+

121SORTED_FORMAT_OPTIONS_STRING = ','.join(SORTED_FORMAT_OPTIONS) 

+

122UNSORTED_FORMAT_OPTIONS_STRING = ','.join( 

+

123 option.replace('true', 'false') for option in SORTED_FORMAT_OPTIONS) 

+

124 

+

125# Defaults 

+

126OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY 

+

127OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED = OUT_RESP_BODY 

+

128OUTPUT_OPTIONS_DEFAULT_OFFLINE = OUT_REQ_HEAD + OUT_REQ_BODY 

+

129 

+

130 

+

131class RequestType(enum.Enum): 

+

132 FORM = enum.auto() 

+

133 MULTIPART = enum.auto() 

+

134 JSON = enum.auto() 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_definition_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_definition_py.html new file mode 100644 index 0000000000..548ee74044 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_definition_py.html @@ -0,0 +1,1055 @@ + + + + + Coverage for httpie\cli\definition.py: 90% + + + + + +
+
+

+ Coverage for httpie\cli\definition.py: + 90% +

+ +

+ 108 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from __future__ import annotations 

+

2 

+

3import os 

+

4import textwrap 

+

5from argparse import FileType 

+

6 

+

7from httpie import __doc__, __version__ 

+

8from httpie.cli.argtypes import (KeyValueArgType, SessionNameValidator, 

+

9 SSLCredentials, readable_file_arg, 

+

10 response_charset_type, response_mime_type) 

+

11from httpie.cli.constants import (BASE_OUTPUT_OPTIONS, DEFAULT_FORMAT_OPTIONS, 

+

12 OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, 

+

13 OUT_RESP_HEAD, OUT_RESP_META, OUTPUT_OPTIONS, 

+

14 OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP, 

+

15 PRETTY_STDOUT_TTY_ONLY, 

+

16 SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY, 

+

17 SORTED_FORMAT_OPTIONS_STRING, 

+

18 UNSORTED_FORMAT_OPTIONS_STRING, RequestType) 

+

19from httpie.cli.options import ParserSpec, Qualifiers, to_argparse 

+

20from httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, BUNDLED_STYLES, 

+

21 get_available_styles) 

+

22from httpie.plugins.builtin import BuiltinAuthPlugin 

+

23from httpie.plugins.registry import plugin_manager 

+

24from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING 

+

25 

+

26 

+

27# Man pages are static (built when making a release). 

+

28# We use this check to not include generated, system-specific information there (e.g., default --ciphers). 

+

29IS_MAN_PAGE = bool(os.environ.get('HTTPIE_BUILDING_MAN_PAGES')) 

+

30 

+

31 

+

32options = ParserSpec( 

+

33 'http', 

+

34 description=f'{__doc__.strip()} <https://httpie.io>', 

+

35 epilog=""" 

+

36 For every --OPTION there is also a --no-OPTION that reverts OPTION 

+

37 to its default value. 

+

38 

+

39 Suggestions and bug reports are greatly appreciated: 

+

40 https://github.com/httpie/cli/issues 

+

41 """, 

+

42 source_file=__file__ 

+

43) 

+

44 

+

45####################################################################### 

+

46# Positional arguments. 

+

47####################################################################### 

+

48 

+

49positional_arguments = options.add_group( 

+

50 'Positional arguments', 

+

51 description=""" 

+

52 These arguments come after any flags and in the order they are listed here. 

+

53 Only URL is required. 

+

54 """, 

+

55) 

+

56 

+

57positional_arguments.add_argument( 

+

58 dest='method', 

+

59 metavar='METHOD', 

+

60 nargs=Qualifiers.OPTIONAL, 

+

61 default=None, 

+

62 short_help='The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).', 

+

63 help=""" 

+

64 The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...). 

+

65 

+

66 This argument can be omitted in which case HTTPie will use POST if there 

+

67 is some data to be sent, otherwise GET: 

+

68 

+

69 $ http example.org # => GET 

+

70 $ http example.org hello=world # => POST 

+

71 

+

72 """, 

+

73) 

+

74positional_arguments.add_argument( 

+

75 dest='url', 

+

76 metavar='URL', 

+

77 short_help='The request URL.', 

+

78 help=""" 

+

79 The request URL. Scheme defaults to 'http://' if the URL 

+

80 does not include one. (You can override this with: --default-scheme=http/https) 

+

81 

+

82 You can also use a shorthand for localhost 

+

83 

+

84 $ http :3000 # => http://localhost:3000 

+

85 $ http :/foo # => http://localhost/foo 

+

86 

+

87 """, 

+

88) 

+

89positional_arguments.add_argument( 

+

90 dest='request_items', 

+

91 metavar='REQUEST_ITEM', 

+

92 nargs=Qualifiers.ZERO_OR_MORE, 

+

93 default=None, 

+

94 type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS), 

+

95 short_help=( 

+

96 'HTTPie’s request items syntax for specifying HTTP headers, JSON/Form' 

+

97 'data, files, and URL parameters.' 

+

98 ), 

+

99 nested_options=[ 

+

100 ('HTTP Headers', 'Name:Value', 'Arbitrary HTTP header, e.g X-API-Token:123'), 

+

101 ('URL Parameters', 'name==value', 'Querystring parameter to the URL, e.g limit==50'), 

+

102 ('Data Fields', 'field=value', 'Data fields to be serialized as JSON (default) or Form Data (with --form)'), 

+

103 ('Raw JSON Fields', 'field:=json', 'Data field for real JSON types.'), 

+

104 ('File upload Fields', 'field@/dir/file', 'Path field for uploading a file.'), 

+

105 ], 

+

106 help=r""" 

+

107 Optional key-value pairs to be included in the request. The separator used 

+

108 determines the type: 

+

109 

+

110 ':' HTTP headers: 

+

111 

+

112 Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0 

+

113 

+

114 '==' URL parameters to be appended to the request URI: 

+

115 

+

116 search==httpie 

+

117 

+

118 '=' Data fields to be serialized into a JSON object (with --json, -j) 

+

119 or form data (with --form, -f): 

+

120 

+

121 name=HTTPie language=Python description='CLI HTTP client' 

+

122 

+

123 ':=' Non-string JSON data fields (only with --json, -j): 

+

124 

+

125 awesome:=true amount:=42 colors:='["red", "green", "blue"]' 

+

126 

+

127 '@' Form file fields (only with --form or --multipart): 

+

128 

+

129 cv@~/Documents/CV.pdf 

+

130 cv@'~/Documents/CV.pdf;type=application/pdf' 

+

131 

+

132 '=@' A data field like '=', but takes a file path and embeds its content: 

+

133 

+

134 essay=@Documents/essay.txt 

+

135 

+

136 ':=@' A raw JSON field like ':=', but takes a file path and embeds its content: 

+

137 

+

138 package:=@./package.json 

+

139 

+

140 You can use a backslash to escape a colliding separator in the field name: 

+

141 

+

142 field-name-with\:colon=value 

+

143 

+

144 """, 

+

145) 

+

146 

+

147####################################################################### 

+

148# Content type. 

+

149####################################################################### 

+

150 

+

151content_types = options.add_group('Predefined content types') 

+

152 

+

153content_types.add_argument( 

+

154 '--json', 

+

155 '-j', 

+

156 action='store_const', 

+

157 const=RequestType.JSON, 

+

158 dest='request_type', 

+

159 short_help='(default) Serialize data items from the command line as a JSON object.', 

+

160 help=""" 

+

161 (default) Data items from the command line are serialized as a JSON object. 

+

162 The Content-Type and Accept headers are set to application/json 

+

163 (if not specified). 

+

164 

+

165 """, 

+

166) 

+

167content_types.add_argument( 

+

168 '--form', 

+

169 '-f', 

+

170 action='store_const', 

+

171 const=RequestType.FORM, 

+

172 dest='request_type', 

+

173 short_help='Serialize data items from the command line as form field data.', 

+

174 help=""" 

+

175 Data items from the command line are serialized as form fields. 

+

176 

+

177 The Content-Type is set to application/x-www-form-urlencoded (if not 

+

178 specified). The presence of any file fields results in a 

+

179 multipart/form-data request. 

+

180 

+

181 """, 

+

182) 

+

183content_types.add_argument( 

+

184 '--multipart', 

+

185 action='store_const', 

+

186 const=RequestType.MULTIPART, 

+

187 dest='request_type', 

+

188 short_help=( 

+

189 'Similar to --form, but always sends a multipart/form-data ' 

+

190 'request (i.e., even without files).' 

+

191 ) 

+

192) 

+

193content_types.add_argument( 

+

194 '--boundary', 

+

195 short_help=( 

+

196 'Specify a custom boundary string for multipart/form-data requests. ' 

+

197 'Only has effect only together with --form.' 

+

198 ) 

+

199) 

+

200content_types.add_argument( 

+

201 '--raw', 

+

202 short_help='Pass raw request data without extra processing.', 

+

203 help=""" 

+

204 This option allows you to pass raw request data without extra processing 

+

205 (as opposed to the structured request items syntax): 

+

206 

+

207 $ http --raw='data' pie.dev/post 

+

208 

+

209 You can achieve the same by piping the data via stdin: 

+

210 

+

211 $ echo data | http pie.dev/post 

+

212 

+

213 Or have HTTPie load the raw data from a file: 

+

214 

+

215 $ http pie.dev/post @data.txt 

+

216 

+

217 

+

218 """, 

+

219) 

+

220 

+

221####################################################################### 

+

222# Content processing. 

+

223####################################################################### 

+

224 

+

225processing_options = options.add_group('Content processing options') 

+

226 

+

227processing_options.add_argument( 

+

228 '--compress', 

+

229 '-x', 

+

230 action='count', 

+

231 default=0, 

+

232 short_help='Compress the content with Deflate algorithm.', 

+

233 help=""" 

+

234 Content compressed (encoded) with Deflate algorithm. 

+

235 The Content-Encoding header is set to deflate. 

+

236 

+

237 Compression is skipped if it appears that compression ratio is 

+

238 negative. Compression can be forced by repeating the argument. 

+

239 

+

240 """, 

+

241) 

+

242 

+

243 

+

244####################################################################### 

+

245# Output processing 

+

246####################################################################### 

+

247 

+

248 

+

249def format_style_help(available_styles, *, isolation_mode: bool = False): 

+

250 text = """ 

+

251 Output coloring style (default is "{default}"). It can be one of: 

+

252 

+

253 {available_styles} 

+

254 """ 

+

255 if isolation_mode: 255 ↛ 256line 255 didn't jump to line 256 because the condition on line 255 was never true

+

256 text += '\n\n' 

+

257 text += 'For finding out all available styles in your system, try:\n\n' 

+

258 text += ' $ http --style\n' 

+

259 text += textwrap.dedent(""" 

+

260 The "{auto_style}" style follows your terminal's ANSI color styles. 

+

261 For non-{auto_style} styles to work properly, please make sure that the 

+

262 $TERM environment variable is set to "xterm-256color" or similar 

+

263 (e.g., via `export TERM=xterm-256color' in your ~/.bashrc). 

+

264 """) 

+

265 

+

266 if isolation_mode: 266 ↛ 267line 266 didn't jump to line 267 because the condition on line 266 was never true

+

267 available_styles = sorted(BUNDLED_STYLES) 

+

268 

+

269 available_styles_text = '\n'.join( 

+

270 f' {line.strip()}' 

+

271 for line in textwrap.wrap(', '.join(available_styles), 60) 

+

272 ).strip() 

+

273 return text.format( 

+

274 default=DEFAULT_STYLE, 

+

275 available_styles=available_styles_text, 

+

276 auto_style=AUTO_STYLE, 

+

277 ) 

+

278 

+

279 

+

280_sorted_kwargs = { 

+

281 'action': 'append_const', 

+

282 'const': SORTED_FORMAT_OPTIONS_STRING, 

+

283 'dest': 'format_options', 

+

284} 

+

285_unsorted_kwargs = { 

+

286 'action': 'append_const', 

+

287 'const': UNSORTED_FORMAT_OPTIONS_STRING, 

+

288 'dest': 'format_options', 

+

289} 

+

290 

+

291output_processing = options.add_group('Output processing') 

+

292 

+

293output_processing.add_argument( 

+

294 '--pretty', 

+

295 dest='prettify', 

+

296 default=PRETTY_STDOUT_TTY_ONLY, 

+

297 choices=sorted(PRETTY_MAP.keys()), 

+

298 short_help='Control the processing of console outputs.', 

+

299 help=""" 

+

300 Controls output processing. The value can be "none" to not prettify 

+

301 the output (default for redirected output), "all" to apply both colors 

+

302 and formatting (default for terminal output), "colors", or "format". 

+

303 

+

304 """, 

+

305) 

+

306output_processing.add_argument( 

+

307 '--style', 

+

308 '-s', 

+

309 dest='style', 

+

310 metavar='STYLE', 

+

311 default=DEFAULT_STYLE, 

+

312 action='lazy_choices', 

+

313 getter=get_available_styles, 

+

314 short_help=f'Output coloring style (default is "{DEFAULT_STYLE}").', 

+

315 help_formatter=format_style_help, 

+

316) 

+

317 

+

318# The closest approx. of the documented resetting to default via --no-<option>. 

+

319# We hide them from the doc because they act only as low-level aliases here. 

+

320output_processing.add_argument( 

+

321 '--no-unsorted', **_sorted_kwargs, help=Qualifiers.SUPPRESS 

+

322) 

+

323output_processing.add_argument( 

+

324 '--no-sorted', **_unsorted_kwargs, help=Qualifiers.SUPPRESS 

+

325) 

+

326 

+

327output_processing.add_argument( 

+

328 '--unsorted', 

+

329 **_unsorted_kwargs, 

+

330 short_help='Disables all sorting while formatting output.', 

+

331 help=f""" 

+

332 Disables all sorting while formatting output. It is a shortcut for: 

+

333 

+

334 --format-options={UNSORTED_FORMAT_OPTIONS_STRING} 

+

335 

+

336 """, 

+

337) 

+

338output_processing.add_argument( 

+

339 '--sorted', 

+

340 **_sorted_kwargs, 

+

341 short_help='Re-enables all sorting options while formatting output.', 

+

342 help=f""" 

+

343 Re-enables all sorting options while formatting output. It is a shortcut for: 

+

344 

+

345 --format-options={SORTED_FORMAT_OPTIONS_STRING} 

+

346 

+

347 """, 

+

348) 

+

349output_processing.add_argument( 

+

350 '--response-charset', 

+

351 metavar='ENCODING', 

+

352 type=response_charset_type, 

+

353 short_help='Override the response encoding for terminal display purposes.', 

+

354 help=""" 

+

355 Override the response encoding for terminal display purposes, e.g.: 

+

356 

+

357 --response-charset=utf8 

+

358 --response-charset=big5 

+

359 

+

360 """, 

+

361) 

+

362output_processing.add_argument( 

+

363 '--response-mime', 

+

364 metavar='MIME_TYPE', 

+

365 type=response_mime_type, 

+

366 short_help='Override the response mime type for coloring and formatting for the terminal.', 

+

367 help=""" 

+

368 Override the response mime type for coloring and formatting for the terminal, e.g.: 

+

369 

+

370 --response-mime=application/json 

+

371 --response-mime=text/xml 

+

372 

+

373 """, 

+

374) 

+

375output_processing.add_argument( 

+

376 '--format-options', 

+

377 action='append', 

+

378 short_help='Controls output formatting.', 

+

379 help=""" 

+

380 Controls output formatting. Only relevant when formatting is enabled 

+

381 through (explicit or implied) --pretty=all or --pretty=format. 

+

382 The following are the default options: 

+

383 

+

384 {option_list} 

+

385 

+

386 You may use this option multiple times, as well as specify multiple 

+

387 comma-separated options at the same time. For example, this modifies the 

+

388 settings to disable the sorting of JSON keys, and sets the indent size to 2: 

+

389 

+

390 --format-options json.sort_keys:false,json.indent:2 

+

391 

+

392 This is something you will typically put into your config file. 

+

393 

+

394 """.format( 

+

395 option_list='\n'.join( 

+

396 f' {option}' for option in DEFAULT_FORMAT_OPTIONS 

+

397 ).strip() 

+

398 ), 

+

399) 

+

400 

+

401####################################################################### 

+

402# Output options 

+

403####################################################################### 

+

404 

+

405output_options = options.add_group('Output options') 

+

406 

+

407output_options.add_argument( 

+

408 '--print', 

+

409 '-p', 

+

410 dest='output_options', 

+

411 metavar='WHAT', 

+

412 short_help='Options to specify what the console output should contain.', 

+

413 help=f""" 

+

414 String specifying what the output should contain: 

+

415 

+

416 '{OUT_REQ_HEAD}' request headers 

+

417 '{OUT_REQ_BODY}' request body 

+

418 '{OUT_RESP_HEAD}' response headers 

+

419 '{OUT_RESP_BODY}' response body 

+

420 '{OUT_RESP_META}' response metadata 

+

421 

+

422 The default behaviour is '{OUTPUT_OPTIONS_DEFAULT}' (i.e., the response 

+

423 headers and body is printed), if standard output is not redirected. 

+

424 If the output is piped to another program or to a file, then only the 

+

425 response body is printed by default. 

+

426 

+

427 """, 

+

428) 

+

429output_options.add_argument( 

+

430 '--headers', 

+

431 '-h', 

+

432 dest='output_options', 

+

433 action='store_const', 

+

434 const=OUT_RESP_HEAD, 

+

435 short_help='Print only the response headers.', 

+

436 help=f""" 

+

437 Print only the response headers. Shortcut for --print={OUT_RESP_HEAD}. 

+

438 

+

439 """, 

+

440) 

+

441output_options.add_argument( 

+

442 '--meta', 

+

443 '-m', 

+

444 dest='output_options', 

+

445 action='store_const', 

+

446 const=OUT_RESP_META, 

+

447 short_help='Print only the response metadata.', 

+

448 help=f""" 

+

449 Print only the response metadata. Shortcut for --print={OUT_RESP_META}. 

+

450 

+

451 """, 

+

452) 

+

453output_options.add_argument( 

+

454 '--body', 

+

455 '-b', 

+

456 dest='output_options', 

+

457 action='store_const', 

+

458 const=OUT_RESP_BODY, 

+

459 short_help='Print only the response body.', 

+

460 help=f""" 

+

461 Print only the response body. Shortcut for --print={OUT_RESP_BODY}. 

+

462 

+

463 """, 

+

464) 

+

465 

+

466output_options.add_argument( 

+

467 '--verbose', 

+

468 '-v', 

+

469 dest='verbose', 

+

470 action='count', 

+

471 default=0, 

+

472 short_help='Make output more verbose.', 

+

473 help=f""" 

+

474 Verbose output. For the level one (with single `-v`/`--verbose`), print 

+

475 the whole request as well as the response. Also print any intermediary 

+

476 requests/responses (such as redirects). For the second level and higher, 

+

477 print these as well as the response metadata. 

+

478 

+

479 Level one is a shortcut for: --all --print={''.join(sorted(BASE_OUTPUT_OPTIONS))} 

+

480 Level two is a shortcut for: --all --print={''.join(sorted(OUTPUT_OPTIONS))} 

+

481 """, 

+

482) 

+

483output_options.add_argument( 

+

484 '--all', 

+

485 default=False, 

+

486 action='store_true', 

+

487 short_help='Show any intermediary requests/responses.', 

+

488 help=""" 

+

489 By default, only the final request/response is shown. Use this flag to show 

+

490 any intermediary requests/responses as well. Intermediary requests include 

+

491 followed redirects (with --follow), the first unauthorized request when 

+

492 Digest auth is used (--auth=digest), etc. 

+

493 

+

494 """, 

+

495) 

+

496output_options.add_argument( 

+

497 '--history-print', 

+

498 '-P', 

+

499 dest='output_options_history', 

+

500 metavar='WHAT', 

+

501 help=Qualifiers.SUPPRESS, 

+

502) 

+

503output_options.add_argument( 

+

504 '--stream', 

+

505 '-S', 

+

506 action='store_true', 

+

507 default=False, 

+

508 short_help='Always stream the response body by line, i.e., behave like `tail -f`.', 

+

509 help=""" 

+

510 Always stream the response body by line, i.e., behave like `tail -f'. 

+

511 

+

512 Without --stream and with --pretty (either set or implied), 

+

513 HTTPie fetches the whole response before it outputs the processed data. 

+

514 

+

515 Set this option when you want to continuously display a prettified 

+

516 long-lived response, such as one from the Twitter streaming API. 

+

517 

+

518 It is useful also without --pretty: It ensures that the output is flushed 

+

519 more often and in smaller chunks. 

+

520 

+

521 """, 

+

522) 

+

523output_options.add_argument( 

+

524 '--output', 

+

525 '-o', 

+

526 type=FileType('a+b'), 

+

527 dest='output_file', 

+

528 metavar='FILE', 

+

529 short_help='Save output to FILE instead of stdout.', 

+

530 help=""" 

+

531 Save output to FILE instead of stdout. If --download is also set, then only 

+

532 the response body is saved to FILE. Other parts of the HTTP exchange are 

+

533 printed to stderr. 

+

534 

+

535 """, 

+

536) 

+

537 

+

538output_options.add_argument( 

+

539 '--download', 

+

540 '-d', 

+

541 action='store_true', 

+

542 default=False, 

+

543 short_help='Download the body to a file instead of printing it to stdout.', 

+

544 help=""" 

+

545 Do not print the response body to stdout. Rather, download it and store it 

+

546 in a file. The filename is guessed unless specified with --output 

+

547 [filename]. This action is similar to the default behaviour of wget. 

+

548 

+

549 """, 

+

550) 

+

551output_options.add_argument( 

+

552 '--continue', 

+

553 '-c', 

+

554 dest='download_resume', 

+

555 action='store_true', 

+

556 default=False, 

+

557 short_help='Resume an interrupted download (--output needs to be specified).', 

+

558 help=""" 

+

559 Resume an interrupted download. Note that the --output option needs to be 

+

560 specified as well. 

+

561 

+

562 """, 

+

563) 

+

564output_options.add_argument( 

+

565 '--quiet', 

+

566 '-q', 

+

567 action='count', 

+

568 default=0, 

+

569 short_help='Do not print to stdout or stderr, except for errors and warnings when provided once.', 

+

570 help=""" 

+

571 Do not print to stdout or stderr, except for errors and warnings when provided once. 

+

572 Provide twice to suppress warnings as well. 

+

573 stdout is still redirected if --output is specified. 

+

574 Flag doesn't affect behaviour of download beyond not printing to terminal. 

+

575 

+

576 """, 

+

577) 

+

578 

+

579####################################################################### 

+

580# Sessions 

+

581####################################################################### 

+

582 

+

583session_name_validator = SessionNameValidator( 

+

584 'Session name contains invalid characters.' 

+

585) 

+

586 

+

587sessions = options.add_group('Sessions', is_mutually_exclusive=True) 

+

588 

+

589sessions.add_argument( 

+

590 '--session', 

+

591 metavar='SESSION_NAME_OR_PATH', 

+

592 type=session_name_validator, 

+

593 short_help='Create, or reuse and update a session.', 

+

594 help=""" 

+

595 Create, or reuse and update a session. Within a session, custom headers, 

+

596 auth credential, as well as any cookies sent by the server persist between 

+

597 requests. 

+

598 

+

599 Session files are stored in: 

+

600 

+

601 [HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json. 

+

602 

+

603 See the following page to find out your default HTTPIE_CONFIG_DIR: 

+

604 

+

605 https://httpie.io/docs/cli/config-file-directory 

+

606 """, 

+

607) 

+

608sessions.add_argument( 

+

609 '--session-read-only', 

+

610 metavar='SESSION_NAME_OR_PATH', 

+

611 type=session_name_validator, 

+

612 short_help='Create or read a session without updating it', 

+

613 help=""" 

+

614 Create or read a session without updating it form the request/response 

+

615 exchange. 

+

616 

+

617 """, 

+

618) 

+

619 

+

620 

+

621####################################################################### 

+

622# Authentication 

+

623####################################################################### 

+

624 

+

625 

+

626def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False): 

+

627 text = """ 

+

628 The authentication mechanism to be used. Defaults to "{default}". 

+

629 

+

630 {auth_types} 

+

631 """ 

+

632 

+

633 auth_plugins = list(auth_plugins_mapping.values()) 

+

634 if isolation_mode: 634 ↛ 635line 634 didn't jump to line 635 because the condition on line 634 was never true

+

635 auth_plugins = [ 

+

636 auth_plugin 

+

637 for auth_plugin in auth_plugins 

+

638 if issubclass(auth_plugin, BuiltinAuthPlugin) 

+

639 ] 

+

640 text += '\n' 

+

641 text += 'To see all available auth types on your system, including ones installed via plugins, run:\n\n' 

+

642 text += ' $ http --auth-type' 

+

643 

+

644 auth_types = '\n\n '.join( 

+

645 '"{type}": {name}{package}{description}'.format( 

+

646 type=plugin.auth_type, 

+

647 name=plugin.name, 

+

648 package=( 

+

649 '' 

+

650 if issubclass(plugin, BuiltinAuthPlugin) 

+

651 else f' (provided by {plugin.package_name})' 

+

652 ), 

+

653 description=( 

+

654 '' 

+

655 if not plugin.description 

+

656 else '\n ' 

+

657 + ('\n '.join(textwrap.wrap(plugin.description))) 

+

658 ), 

+

659 ) 

+

660 for plugin in auth_plugins 

+

661 ) 

+

662 

+

663 return text.format( 

+

664 default=auth_plugins[0].auth_type, 

+

665 auth_types=auth_types, 

+

666 ) 

+

667 

+

668 

+

669authentication = options.add_group('Authentication') 

+

670 

+

671authentication.add_argument( 

+

672 '--auth', 

+

673 '-a', 

+

674 default=None, 

+

675 metavar='USER[:PASS] | TOKEN', 

+

676 short_help='Credentials for the selected (-A) authentication method.', 

+

677 help=""" 

+

678 For username/password based authentication mechanisms (e.g 

+

679 basic auth or digest auth) if only the username is provided 

+

680 (-a username), HTTPie will prompt for the password. 

+

681 

+

682 """, 

+

683) 

+

684authentication.add_argument( 

+

685 '--auth-type', 

+

686 '-A', 

+

687 action='lazy_choices', 

+

688 default=None, 

+

689 getter=plugin_manager.get_auth_plugin_mapping, 

+

690 sort=True, 

+

691 cache=False, 

+

692 short_help='The authentication mechanism to be used.', 

+

693 help_formatter=format_auth_help, 

+

694) 

+

695authentication.add_argument( 

+

696 '--ignore-netrc', 

+

697 default=False, 

+

698 action='store_true', 

+

699 short_help='Ignore credentials from .netrc.' 

+

700) 

+

701 

+

702####################################################################### 

+

703# Network 

+

704####################################################################### 

+

705 

+

706network = options.add_group('Network') 

+

707 

+

708network.add_argument( 

+

709 '--offline', 

+

710 default=False, 

+

711 action='store_true', 

+

712 short_help='Build the request and print it but don’t actually send it.' 

+

713) 

+

714network.add_argument( 

+

715 '--proxy', 

+

716 default=[], 

+

717 action='append', 

+

718 metavar='PROTOCOL:PROXY_URL', 

+

719 type=KeyValueArgType(SEPARATOR_PROXY), 

+

720 short_help='String mapping of protocol to the URL of the proxy.', 

+

721 help=""" 

+

722 String mapping protocol to the URL of the proxy 

+

723 (e.g. http:http://foo.bar:3128). You can specify multiple proxies with 

+

724 different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY, 

+

725 and $HTTPS_proxy are supported as well. 

+

726 

+

727 """, 

+

728) 

+

729network.add_argument( 

+

730 '--follow', 

+

731 '-F', 

+

732 default=False, 

+

733 action='store_true', 

+

734 short_help='Follow 30x Location redirects.' 

+

735) 

+

736 

+

737network.add_argument( 

+

738 '--max-redirects', 

+

739 type=int, 

+

740 default=30, 

+

741 short_help='The maximum number of redirects that should be followed (with --follow).', 

+

742 help=""" 

+

743 By default, requests have a limit of 30 redirects (works with --follow). 

+

744 

+

745 """, 

+

746) 

+

747network.add_argument( 

+

748 '--max-headers', 

+

749 type=int, 

+

750 default=0, 

+

751 short_help=( 

+

752 'The maximum number of response headers to be read before ' 

+

753 'giving up (default 0, i.e., no limit).' 

+

754 ) 

+

755) 

+

756 

+

757network.add_argument( 

+

758 '--timeout', 

+

759 type=float, 

+

760 default=0, 

+

761 metavar='SECONDS', 

+

762 short_help='The connection timeout of the request in seconds.', 

+

763 help=""" 

+

764 The connection timeout of the request in seconds. 

+

765 The default value is 0, i.e., there is no timeout limit. 

+

766 This is not a time limit on the entire response download; 

+

767 rather, an error is reported if the server has not issued a response for 

+

768 timeout seconds (more precisely, if no bytes have been received on 

+

769 the underlying socket for timeout seconds). 

+

770 

+

771 """, 

+

772) 

+

773network.add_argument( 

+

774 '--check-status', 

+

775 default=False, 

+

776 action='store_true', 

+

777 short_help='Exit with an error status code if the server replies with an error.', 

+

778 help=""" 

+

779 By default, HTTPie exits with 0 when no network or other fatal errors 

+

780 occur. This flag instructs HTTPie to also check the HTTP status code and 

+

781 exit with an error if the status indicates one. 

+

782 

+

783 When the server replies with a 4xx (Client Error) or 5xx (Server Error) 

+

784 status code, HTTPie exits with 4 or 5 respectively. If the response is a 

+

785 3xx (Redirect) and --follow hasn't been set, then the exit status is 3. 

+

786 Also an error message is written to stderr if stdout is redirected. 

+

787 

+

788 """, 

+

789) 

+

790network.add_argument( 

+

791 '--path-as-is', 

+

792 default=False, 

+

793 action='store_true', 

+

794 short_help='Bypass dot segment (/../ or /./) URL squashing.' 

+

795) 

+

796network.add_argument( 

+

797 '--chunked', 

+

798 default=False, 

+

799 action='store_true', 

+

800 short_help=( 

+

801 'Enable streaming via chunked transfer encoding. ' 

+

802 'The Transfer-Encoding header is set to chunked.' 

+

803 ) 

+

804) 

+

805 

+

806####################################################################### 

+

807# SSL 

+

808####################################################################### 

+

809 

+

810ssl = options.add_group('SSL') 

+

811 

+

812ssl.add_argument( 

+

813 '--verify', 

+

814 default='yes', 

+

815 short_help='If "no", skip SSL verification. If a file path, use it as a CA bundle.', 

+

816 help=""" 

+

817 Set to "no" (or "false") to skip checking the host's SSL certificate. 

+

818 Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file 

+

819 for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment 

+

820 variable instead.) 

+

821 """, 

+

822) 

+

823ssl.add_argument( 

+

824 '--ssl', 

+

825 dest='ssl_version', 

+

826 choices=sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys()), 

+

827 short_help='The desired protocol version to used.', 

+

828 help=""" 

+

829 The desired protocol version to use. This will default to 

+

830 SSL v2.3 which will negotiate the highest protocol that both 

+

831 the server and your installation of OpenSSL support. Available protocols 

+

832 may vary depending on OpenSSL installation (only the supported ones 

+

833 are shown here). 

+

834 

+

835 """, 

+

836) 

+

837 

+

838CIPHERS_CURRENT_DEFAULTS = ( 

+

839 """ 

+

840 See `http --help` for the default ciphers list on you system. 

+

841 

+

842 """ 

+

843 if IS_MAN_PAGE else 

+

844 f""" 

+

845 By default, the following ciphers are used on your system: 

+

846 

+

847 {DEFAULT_SSL_CIPHERS_STRING} 

+

848 

+

849 """ 

+

850) 

+

851ssl.add_argument( 

+

852 '--ciphers', 

+

853 short_help='A string in the OpenSSL cipher list format.', 

+

854 help=f""" 

+

855 

+

856 A string in the OpenSSL cipher list format. 

+

857 

+

858 {CIPHERS_CURRENT_DEFAULTS} 

+

859 

+

860 """ 

+

861) 

+

862ssl.add_argument( 

+

863 '--cert', 

+

864 default=None, 

+

865 type=readable_file_arg, 

+

866 short_help='Specifies a local cert to use as the client-side SSL certificate.', 

+

867 help=""" 

+

868 You can specify a local cert to use as client side SSL certificate. 

+

869 This file may either contain both private key and certificate or you may 

+

870 specify --cert-key separately. 

+

871 

+

872 """, 

+

873) 

+

874ssl.add_argument( 

+

875 '--cert-key', 

+

876 default=None, 

+

877 type=readable_file_arg, 

+

878 short_help='The private key to use with SSL. Only needed if --cert is given.', 

+

879 help=""" 

+

880 The private key to use with SSL. Only needed if --cert is given and the 

+

881 certificate file does not contain the private key. 

+

882 

+

883 """, 

+

884) 

+

885 

+

886ssl.add_argument( 

+

887 '--cert-key-pass', 

+

888 default=None, 

+

889 type=SSLCredentials, 

+

890 short_help='The passphrase to be used to with the given private key.', 

+

891 help=""" 

+

892 The passphrase to be used to with the given private key. Only needed if --cert-key 

+

893 is given and the key file requires a passphrase. 

+

894 If not provided, you’ll be prompted interactively. 

+

895 """ 

+

896) 

+

897 

+

898####################################################################### 

+

899# Troubleshooting 

+

900####################################################################### 

+

901 

+

902troubleshooting = options.add_group('Troubleshooting') 

+

903troubleshooting.add_argument( 

+

904 '--ignore-stdin', 

+

905 '-I', 

+

906 action='store_true', 

+

907 default=False, 

+

908 short_help='Do not attempt to read stdin' 

+

909) 

+

910troubleshooting.add_argument( 

+

911 '--help', 

+

912 action='help', 

+

913 default=Qualifiers.SUPPRESS, 

+

914 short_help='Show this help message and exit.', 

+

915) 

+

916troubleshooting.add_argument( 

+

917 '--manual', 

+

918 action='manual', 

+

919 default=Qualifiers.SUPPRESS, 

+

920 short_help='Show the full manual.', 

+

921) 

+

922troubleshooting.add_argument( 

+

923 '--version', 

+

924 action='version', 

+

925 version=__version__, 

+

926 short_help='Show version and exit.', 

+

927) 

+

928troubleshooting.add_argument( 

+

929 '--traceback', 

+

930 action='store_true', 

+

931 default=False, 

+

932 short_help='Prints the exception traceback should one occur.', 

+

933) 

+

934troubleshooting.add_argument( 

+

935 '--default-scheme', 

+

936 default='http', 

+

937 short_help='The default scheme to use if not specified in the URL.' 

+

938) 

+

939troubleshooting.add_argument( 

+

940 '--debug', 

+

941 action='store_true', 

+

942 default=False, 

+

943 short_help='Print useful diagnostic information for bug reports.', 

+

944 help=""" 

+

945 Prints the exception traceback should one occur, as well as other 

+

946 information useful for debugging HTTPie itself and for reporting bugs. 

+

947 

+

948 """, 

+

949) 

+

950 

+

951####################################################################### 

+

952# Finalization 

+

953####################################################################### 

+

954 

+

955options.finalize() 

+

956parser = to_argparse(options) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_dicts_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_dicts_py.html new file mode 100644 index 0000000000..cd78dfd212 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_dicts_py.html @@ -0,0 +1,193 @@ + + + + + Coverage for httpie\cli\dicts.py: 95% + + + + + +
+
+

+ Coverage for httpie\cli\dicts.py: + 95% +

+ +

+ 40 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from collections import OrderedDict 

+

2 

+

3from multidict import MultiDict, CIMultiDict 

+

4 

+

5 

+

6class BaseMultiDict(MultiDict): 

+

7 """ 

+

8 Base class for all MultiDicts. 

+

9 """ 

+

10 

+

11 

+

12class HTTPHeadersDict(CIMultiDict, BaseMultiDict): 

+

13 """ 

+

14 Headers are case-insensitive and multiple values are supported 

+

15 through the `add()` API. 

+

16 """ 

+

17 

+

18 def add(self, key, value): 

+

19 """ 

+

20 Add or update a new header. 

+

21 

+

22 If the given `value` is `None`, then all the previous 

+

23 values will be overwritten and the value will be set 

+

24 to `None`. 

+

25 """ 

+

26 if value is None: 

+

27 self[key] = value 

+

28 return None 

+

29 

+

30 # If the previous value for the given header is `None` 

+

31 # then discard it since we are explicitly giving a new 

+

32 # value for it. 

+

33 if key in self and self.getone(key) is None: 

+

34 self.popone(key) 

+

35 

+

36 super().add(key, value) 

+

37 

+

38 def remove_item(self, key, value): 

+

39 """ 

+

40 Remove a (key, value) pair from the dict. 

+

41 """ 

+

42 existing_values = self.popall(key) 

+

43 existing_values.remove(value) 

+

44 

+

45 for value in existing_values: 45 ↛ 46line 45 didn't jump to line 46 because the loop on line 45 never started

+

46 self.add(key, value) 

+

47 

+

48 

+

49class RequestJSONDataDict(OrderedDict): 

+

50 pass 

+

51 

+

52 

+

53class MultiValueOrderedDict(OrderedDict): 

+

54 """Multi-value dict for URL parameters and form data.""" 

+

55 

+

56 def __setitem__(self, key, value): 

+

57 """ 

+

58 If `key` is assigned more than once, `self[key]` holds a 

+

59 `list` of all the values. 

+

60 

+

61 This allows having multiple fields with the same name in form 

+

62 data and URL params. 

+

63 

+

64 """ 

+

65 assert not isinstance(value, list) 

+

66 if key not in self: 

+

67 super().__setitem__(key, value) 

+

68 else: 

+

69 if not isinstance(self[key], list): 69 ↛ 71line 69 didn't jump to line 71 because the condition on line 69 was always true

+

70 super().__setitem__(key, [self[key]]) 

+

71 self[key].append(value) 

+

72 

+

73 def items(self): 

+

74 for key, values in super().items(): 

+

75 if not isinstance(values, list): 

+

76 values = [values] 

+

77 for value in values: 

+

78 yield key, value 

+

79 

+

80 

+

81class RequestQueryParamsDict(MultiValueOrderedDict): 

+

82 pass 

+

83 

+

84 

+

85class RequestDataDict(MultiValueOrderedDict): 

+

86 pass 

+

87 

+

88 

+

89class MultipartRequestDataDict(MultiValueOrderedDict): 

+

90 pass 

+

91 

+

92 

+

93class RequestFilesDict(RequestDataDict): 

+

94 pass 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_exceptions_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_exceptions_py.html new file mode 100644 index 0000000000..30d711d7df --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_exceptions_py.html @@ -0,0 +1,101 @@ + + + + + Coverage for httpie\cli\exceptions.py: 100% + + + + + +
+
+

+ Coverage for httpie\cli\exceptions.py: + 100% +

+ +

+ 2 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1class ParseError(Exception): 

+

2 pass 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_options_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_options_py.html new file mode 100644 index 0000000000..27be66a403 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_options_py.html @@ -0,0 +1,348 @@ + + + + + Coverage for httpie\cli\options.py: 97% + + + + + +
+
+

+ Coverage for httpie\cli\options.py: + 97% +

+ +

+ 124 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import textwrap 

+

3import typing 

+

4from dataclasses import dataclass, field 

+

5from enum import Enum, auto 

+

6from typing import Any, Optional, Dict, List, Tuple, Type, TypeVar 

+

7 

+

8from httpie.cli.argparser import HTTPieArgumentParser 

+

9from httpie.cli.utils import Manual, LazyChoices 

+

10 

+

11 

+

12class Qualifiers(Enum): 

+

13 OPTIONAL = auto() 

+

14 ZERO_OR_MORE = auto() 

+

15 ONE_OR_MORE = auto() 

+

16 SUPPRESS = auto() 

+

17 

+

18 

+

19def map_qualifiers( 

+

20 configuration: Dict[str, Any], qualifier_map: Dict[Qualifiers, Any] 

+

21) -> Dict[str, Any]: 

+

22 return { 

+

23 key: qualifier_map[value] if isinstance(value, Qualifiers) else value 

+

24 for key, value in configuration.items() 

+

25 } 

+

26 

+

27 

+

28def drop_keys( 

+

29 configuration: Dict[str, Any], key_blacklist: Tuple[str, ...] 

+

30): 

+

31 return { 

+

32 key: value 

+

33 for key, value in configuration.items() 

+

34 if key not in key_blacklist 

+

35 } 

+

36 

+

37 

+

38PARSER_SPEC_VERSION = '0.0.1a0' 

+

39 

+

40 

+

41@dataclass 

+

42class ParserSpec: 

+

43 program: str 

+

44 description: Optional[str] = None 

+

45 epilog: Optional[str] = None 

+

46 groups: List['Group'] = field(default_factory=list) 

+

47 man_page_hint: Optional[str] = None 

+

48 source_file: Optional[str] = None 

+

49 

+

50 def finalize(self) -> 'ParserSpec': 

+

51 if self.description: 

+

52 self.description = textwrap.dedent(self.description) 

+

53 if self.epilog: 

+

54 self.epilog = textwrap.dedent(self.epilog) 

+

55 for group in self.groups: 

+

56 group.finalize() 

+

57 return self 

+

58 

+

59 def add_group(self, name: str, **kwargs) -> 'Group': 

+

60 group = Group(name, **kwargs) 

+

61 self.groups.append(group) 

+

62 return group 

+

63 

+

64 def serialize(self) -> Dict[str, Any]: 

+

65 return { 

+

66 'name': self.program, 

+

67 'description': self.description, 

+

68 'groups': [group.serialize() for group in self.groups], 

+

69 } 

+

70 

+

71 

+

72@dataclass 

+

73class Group: 

+

74 name: str 

+

75 description: str = '' 

+

76 is_mutually_exclusive: bool = False 

+

77 arguments: List['Argument'] = field(default_factory=list) 

+

78 

+

79 def finalize(self) -> None: 

+

80 if self.description: 

+

81 self.description = textwrap.dedent(self.description) 

+

82 

+

83 def add_argument(self, *args, **kwargs): 

+

84 argument = Argument(list(args), kwargs.copy()) 

+

85 argument.post_init() 

+

86 self.arguments.append(argument) 

+

87 return argument 

+

88 

+

89 def serialize(self) -> Dict[str, Any]: 

+

90 return { 

+

91 'name': self.name, 

+

92 'description': self.description or None, 

+

93 'is_mutually_exclusive': self.is_mutually_exclusive, 

+

94 'args': [argument.serialize() for argument in self.arguments], 

+

95 } 

+

96 

+

97 

+

98class Argument(typing.NamedTuple): 

+

99 aliases: List[str] 

+

100 configuration: Dict[str, Any] 

+

101 

+

102 def post_init(self): 

+

103 """Run a bunch of post-init hooks.""" 

+

104 # If there is a short help, then create the longer version from it. 

+

105 short_help = self.configuration.get('short_help') 

+

106 if ( 

+

107 short_help 

+

108 and 'help' not in self.configuration 

+

109 and self.configuration.get('action') != 'lazy_choices' 

+

110 ): 

+

111 self.configuration['help'] = f'\n{short_help}\n\n' 

+

112 

+

113 def serialize(self, *, isolation_mode: bool = False) -> Dict[str, Any]: 

+

114 configuration = self.configuration.copy() 

+

115 

+

116 # Unpack the dynamically computed choices, since we 

+

117 # will need to store the actual values somewhere. 

+

118 action = configuration.pop('action', None) 

+

119 short_help = configuration.pop('short_help', None) 

+

120 nested_options = configuration.pop('nested_options', None) 

+

121 

+

122 if action == 'lazy_choices': 

+

123 choices = LazyChoices( 

+

124 self.aliases, 

+

125 **{'dest': None, **configuration}, 

+

126 isolation_mode=isolation_mode 

+

127 ) 

+

128 configuration['choices'] = list(choices.load()) 

+

129 configuration['help'] = choices.help 

+

130 

+

131 result = {} 

+

132 if self.aliases: 

+

133 result['options'] = self.aliases.copy() 

+

134 else: 

+

135 result['options'] = [configuration['metavar']] 

+

136 result['is_positional'] = True 

+

137 

+

138 qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)] 

+

139 result.update(qualifiers) 

+

140 

+

141 description = configuration.get('help') 

+

142 if description and description is not Qualifiers.SUPPRESS: 

+

143 result['short_description'] = short_help 

+

144 result['description'] = description 

+

145 

+

146 if nested_options: 

+

147 result['nested_options'] = nested_options 

+

148 

+

149 python_type = configuration.get('type') 

+

150 if python_type is not None: 

+

151 if hasattr(python_type, '__name__'): 

+

152 type_name = python_type.__name__ 

+

153 else: 

+

154 type_name = type(python_type).__name__ 

+

155 

+

156 result['python_type_name'] = type_name 

+

157 

+

158 result.update({ 

+

159 key: value 

+

160 for key, value in configuration.items() 

+

161 if key in JSON_DIRECT_MIRROR_OPTIONS 

+

162 if value is not Qualifiers.SUPPRESS 

+

163 }) 

+

164 

+

165 return result 

+

166 

+

167 @property 

+

168 def is_positional(self): 

+

169 return len(self.aliases) == 0 

+

170 

+

171 @property 

+

172 def is_hidden(self): 

+

173 return self.configuration.get('help') is Qualifiers.SUPPRESS 

+

174 

+

175 def __getattr__(self, attribute_name): 

+

176 if attribute_name in self.configuration: 176 ↛ 179line 176 didn't jump to line 179 because the condition on line 176 was always true

+

177 return self.configuration[attribute_name] 

+

178 else: 

+

179 raise AttributeError(attribute_name) 

+

180 

+

181 

+

182ParserType = TypeVar('ParserType', bound=Type[argparse.ArgumentParser]) 

+

183 

+

184ARGPARSE_QUALIFIER_MAP = { 

+

185 Qualifiers.OPTIONAL: argparse.OPTIONAL, 

+

186 Qualifiers.SUPPRESS: argparse.SUPPRESS, 

+

187 Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE, 

+

188 Qualifiers.ONE_OR_MORE: argparse.ONE_OR_MORE 

+

189} 

+

190ARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options') 

+

191 

+

192 

+

193def to_argparse( 

+

194 abstract_options: ParserSpec, 

+

195 parser_type: ParserType = HTTPieArgumentParser, 

+

196) -> ParserType: 

+

197 concrete_parser = parser_type( 

+

198 prog=abstract_options.program, 

+

199 description=abstract_options.description, 

+

200 epilog=abstract_options.epilog, 

+

201 ) 

+

202 concrete_parser.spec = abstract_options 

+

203 concrete_parser.register('action', 'lazy_choices', LazyChoices) 

+

204 concrete_parser.register('action', 'manual', Manual) 

+

205 

+

206 for abstract_group in abstract_options.groups: 

+

207 concrete_group = concrete_parser.add_argument_group( 

+

208 title=abstract_group.name, description=abstract_group.description 

+

209 ) 

+

210 if abstract_group.is_mutually_exclusive: 

+

211 concrete_group = concrete_group.add_mutually_exclusive_group(required=False) 

+

212 

+

213 for abstract_argument in abstract_group.arguments: 

+

214 concrete_group.add_argument( 

+

215 *abstract_argument.aliases, 

+

216 **drop_keys(map_qualifiers( 

+

217 abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP 

+

218 ), ARGPARSE_IGNORE_KEYS) 

+

219 ) 

+

220 

+

221 return concrete_parser 

+

222 

+

223 

+

224JSON_DIRECT_MIRROR_OPTIONS = ( 

+

225 'choices', 

+

226 'metavar' 

+

227) 

+

228 

+

229 

+

230JSON_QUALIFIER_TO_OPTIONS = { 

+

231 Qualifiers.OPTIONAL: {'is_optional': True}, 

+

232 Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True}, 

+

233 Qualifiers.ONE_OR_MORE: {'is_optional': False, 'is_variadic': True}, 

+

234 Qualifiers.SUPPRESS: {} 

+

235} 

+

236 

+

237 

+

238def to_data(abstract_options: ParserSpec) -> Dict[str, Any]: 

+

239 return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()} 

+

240 

+

241 

+

242def parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec: 

+

243 """Take an existing argparse parser, and create a spec from it.""" 

+

244 return ParserSpec( 

+

245 program=parser.prog, 

+

246 description=parser.description, 

+

247 epilog=parser.epilog, 

+

248 **kwargs 

+

249 ) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_requestitems_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_requestitems_py.html new file mode 100644 index 0000000000..871109d75e --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_requestitems_py.html @@ -0,0 +1,329 @@ + + + + + Coverage for httpie\cli\requestitems.py: 93% + + + + + +
+
+

+ Coverage for httpie\cli\requestitems.py: + 93% +

+ +

+ 100 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import os 

+

2import functools 

+

3from typing import Callable, Dict, IO, List, Optional, Tuple, Union 

+

4 

+

5from .argtypes import KeyValueArg 

+

6from .constants import ( 

+

7 SEPARATORS_GROUP_MULTIPART, SEPARATOR_DATA_EMBED_FILE_CONTENTS, 

+

8 SEPARATOR_DATA_EMBED_RAW_JSON_FILE, SEPARATOR_GROUP_NESTED_JSON_ITEMS, 

+

9 SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD, 

+

10 SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY, 

+

11 SEPARATOR_HEADER_EMBED, SEPARATOR_QUERY_PARAM, 

+

12 SEPARATOR_QUERY_EMBED_FILE, RequestType 

+

13) 

+

14from .dicts import ( 

+

15 BaseMultiDict, MultipartRequestDataDict, RequestDataDict, 

+

16 RequestFilesDict, HTTPHeadersDict, RequestJSONDataDict, 

+

17 RequestQueryParamsDict, 

+

18) 

+

19from .exceptions import ParseError 

+

20from .nested_json import interpret_nested_json 

+

21from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split_iterable 

+

22 

+

23 

+

24class RequestItems: 

+

25 

+

26 def __init__(self, request_type: Optional[RequestType] = None): 

+

27 self.headers = HTTPHeadersDict() 

+

28 self.request_type = request_type 

+

29 self.is_json = request_type is None or request_type is RequestType.JSON 

+

30 self.data = RequestJSONDataDict() if self.is_json else RequestDataDict() 

+

31 self.files = RequestFilesDict() 

+

32 self.params = RequestQueryParamsDict() 

+

33 # To preserve the order of fields in file upload multipart requests. 

+

34 self.multipart_data = MultipartRequestDataDict() 

+

35 

+

36 @classmethod 

+

37 def from_args( 

+

38 cls, 

+

39 request_item_args: List[KeyValueArg], 

+

40 request_type: Optional[RequestType] = None, 

+

41 ) -> 'RequestItems': 

+

42 instance = cls(request_type=request_type) 

+

43 rules: Dict[str, Tuple[Callable, dict]] = { 

+

44 SEPARATOR_HEADER: ( 

+

45 process_header_arg, 

+

46 instance.headers, 

+

47 ), 

+

48 SEPARATOR_HEADER_EMPTY: ( 

+

49 process_empty_header_arg, 

+

50 instance.headers, 

+

51 ), 

+

52 SEPARATOR_HEADER_EMBED: ( 

+

53 process_embed_header_arg, 

+

54 instance.headers, 

+

55 ), 

+

56 SEPARATOR_QUERY_PARAM: ( 

+

57 process_query_param_arg, 

+

58 instance.params, 

+

59 ), 

+

60 SEPARATOR_QUERY_EMBED_FILE: ( 

+

61 process_embed_query_param_arg, 

+

62 instance.params, 

+

63 ), 

+

64 SEPARATOR_FILE_UPLOAD: ( 

+

65 process_file_upload_arg, 

+

66 instance.files, 

+

67 ), 

+

68 SEPARATOR_DATA_STRING: ( 

+

69 process_data_item_arg, 

+

70 instance.data, 

+

71 ), 

+

72 SEPARATOR_DATA_EMBED_FILE_CONTENTS: ( 

+

73 process_data_embed_file_contents_arg, 

+

74 instance.data, 

+

75 ), 

+

76 SEPARATOR_GROUP_NESTED_JSON_ITEMS: ( 

+

77 process_data_nested_json_embed_args, 

+

78 instance.data, 

+

79 ), 

+

80 SEPARATOR_DATA_RAW_JSON: ( 

+

81 convert_json_value_to_form_if_needed( 

+

82 in_json_mode=instance.is_json, 

+

83 processor=process_data_raw_json_embed_arg 

+

84 ), 

+

85 instance.data, 

+

86 ), 

+

87 SEPARATOR_DATA_EMBED_RAW_JSON_FILE: ( 

+

88 convert_json_value_to_form_if_needed( 

+

89 in_json_mode=instance.is_json, 

+

90 processor=process_data_embed_raw_json_file_arg, 

+

91 ), 

+

92 instance.data, 

+

93 ), 

+

94 } 

+

95 

+

96 if instance.is_json: 

+

97 json_item_args, request_item_args = split_iterable( 

+

98 iterable=request_item_args, 

+

99 key=lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS 

+

100 ) 

+

101 if json_item_args: 

+

102 pairs = [(arg.key, rules[arg.sep][0](arg)) for arg in json_item_args] 

+

103 processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS] 

+

104 value = processor_func(pairs) 

+

105 target_dict.update(value) 

+

106 

+

107 # Then handle all other items. 

+

108 for arg in request_item_args: 

+

109 processor_func, target_dict = rules[arg.sep] 

+

110 value = processor_func(arg) 

+

111 

+

112 if arg.sep in SEPARATORS_GROUP_MULTIPART: 

+

113 instance.multipart_data[arg.key] = value 

+

114 

+

115 if isinstance(target_dict, BaseMultiDict): 

+

116 target_dict.add(arg.key, value) 

+

117 else: 

+

118 target_dict[arg.key] = value 

+

119 

+

120 return instance 

+

121 

+

122 

+

123JSONType = Union[str, bool, int, list, dict] 

+

124 

+

125 

+

126def process_header_arg(arg: KeyValueArg) -> Optional[str]: 

+

127 return arg.value or None 

+

128 

+

129 

+

130def process_embed_header_arg(arg: KeyValueArg) -> str: 

+

131 return load_text_file(arg).rstrip('\n') 

+

132 

+

133 

+

134def process_empty_header_arg(arg: KeyValueArg) -> str: 

+

135 if not arg.value: 

+

136 return arg.value 

+

137 raise ParseError( 

+

138 f'Invalid item {arg.orig!r} (to specify an empty header use `Header;`)' 

+

139 ) 

+

140 

+

141 

+

142def process_query_param_arg(arg: KeyValueArg) -> str: 

+

143 return arg.value 

+

144 

+

145 

+

146def process_embed_query_param_arg(arg: KeyValueArg) -> str: 

+

147 return load_text_file(arg).rstrip('\n') 

+

148 

+

149 

+

150def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]: 

+

151 parts = arg.value.split(SEPARATOR_FILE_UPLOAD_TYPE) 

+

152 filename = parts[0] 

+

153 mime_type = parts[1] if len(parts) > 1 else None 

+

154 try: 

+

155 f = open(os.path.expanduser(filename), 'rb') 

+

156 except OSError as e: 

+

157 raise ParseError(f'{arg.orig!r}: {e}') 

+

158 return ( 

+

159 os.path.basename(filename), 

+

160 f, 

+

161 mime_type or get_content_type(filename), 

+

162 ) 

+

163 

+

164 

+

165def convert_json_value_to_form_if_needed(in_json_mode: bool, processor: Callable[[KeyValueArg], JSONType]) -> Callable[[], str]: 

+

166 """ 

+

167 We allow primitive values to be passed to forms via JSON key/value syntax. 

+

168 

+

169 But complex values lead to an error because there’s no clear way to serialize them. 

+

170 

+

171 """ 

+

172 if in_json_mode: 

+

173 return processor 

+

174 

+

175 @functools.wraps(processor) 

+

176 def wrapper(*args, **kwargs) -> str: 

+

177 try: 

+

178 output = processor(*args, **kwargs) 

+

179 except ParseError: 

+

180 output = None 

+

181 if isinstance(output, (str, int, float)): 

+

182 return str(output) 

+

183 else: 

+

184 raise ParseError('Cannot use complex JSON value types with --form/--multipart.') 

+

185 

+

186 return wrapper 

+

187 

+

188 

+

189def process_data_item_arg(arg: KeyValueArg) -> str: 

+

190 return arg.value 

+

191 

+

192 

+

193def process_data_embed_file_contents_arg(arg: KeyValueArg) -> str: 

+

194 return load_text_file(arg) 

+

195 

+

196 

+

197def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType: 

+

198 contents = load_text_file(arg) 

+

199 value = load_json(arg, contents) 

+

200 return value 

+

201 

+

202 

+

203def process_data_raw_json_embed_arg(arg: KeyValueArg) -> JSONType: 

+

204 value = load_json(arg, arg.value) 

+

205 return value 

+

206 

+

207 

+

208def process_data_nested_json_embed_args(pairs) -> Dict[str, JSONType]: 

+

209 return interpret_nested_json(pairs) 

+

210 

+

211 

+

212def load_text_file(item: KeyValueArg) -> str: 

+

213 path = item.value 

+

214 try: 

+

215 with open(os.path.expanduser(path), 'rb') as f: 

+

216 return f.read().decode() 

+

217 except OSError as e: 

+

218 raise ParseError(f'{item.orig!r}: {e}') 

+

219 except UnicodeDecodeError: 

+

220 raise ParseError( 

+

221 f'{item.orig!r}: cannot embed the content of {item.value!r},' 

+

222 ' not a UTF-8 or ASCII-encoded text file' 

+

223 ) 

+

224 

+

225 

+

226def load_json(arg: KeyValueArg, contents: str) -> JSONType: 

+

227 try: 

+

228 return load_json_preserve_order_and_dupe_keys(contents) 

+

229 except ValueError as e: 

+

230 raise ParseError(f'{arg.orig!r}: {e}') 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_utils_py.html b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_utils_py.html new file mode 100644 index 0000000000..07828ec35b --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_6ee826626d1566e3_utils_py.html @@ -0,0 +1,178 @@ + + + + + Coverage for httpie\cli\utils.py: 96% + + + + + +
+
+

+ Coverage for httpie\cli\utils.py: + 96% +

+ +

+ 41 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from typing import Any, Callable, Generic, Iterator, Iterable, Optional, TypeVar 

+

3 

+

4T = TypeVar('T') 

+

5 

+

6 

+

7class Manual(argparse.Action): 

+

8 def __init__( 

+

9 self, 

+

10 option_strings, 

+

11 dest=argparse.SUPPRESS, 

+

12 default=argparse.SUPPRESS, 

+

13 help=None 

+

14 ): 

+

15 super().__init__( 

+

16 option_strings=option_strings, 

+

17 dest=dest, 

+

18 default=default, 

+

19 nargs=0, 

+

20 help=help 

+

21 ) 

+

22 

+

23 def __call__(self, parser, namespace, values, option_string=None): 

+

24 parser.print_manual() 

+

25 parser.exit() 

+

26 

+

27 

+

28class LazyChoices(argparse.Action, Generic[T]): 

+

29 def __init__( 

+

30 self, 

+

31 *args, 

+

32 getter: Callable[[], Iterable[T]], 

+

33 help_formatter: Optional[Callable[[T, bool], str]] = None, 

+

34 sort: bool = False, 

+

35 cache: bool = True, 

+

36 isolation_mode: bool = False, 

+

37 **kwargs 

+

38 ) -> None: 

+

39 self.getter = getter 

+

40 self.help_formatter = help_formatter 

+

41 self.sort = sort 

+

42 self.cache = cache 

+

43 self.isolation_mode = isolation_mode 

+

44 self._help: Optional[str] = None 

+

45 self._obj: Optional[Iterable[T]] = None 

+

46 super().__init__(*args, **kwargs) 

+

47 self.choices = self 

+

48 

+

49 def load(self) -> T: 

+

50 if self._obj is None or not self.cache: 

+

51 self._obj = self.getter() 

+

52 

+

53 assert self._obj is not None 

+

54 return self._obj 

+

55 

+

56 @property 

+

57 def help(self) -> str: 

+

58 if self._help is None and self.help_formatter is not None: 

+

59 self._help = self.help_formatter( 

+

60 self.load(), 

+

61 isolation_mode=self.isolation_mode 

+

62 ) 

+

63 return self._help 

+

64 

+

65 @help.setter 

+

66 def help(self, value: Any) -> None: 

+

67 self._help = value 

+

68 

+

69 def __contains__(self, item: Any) -> bool: 

+

70 return item in self.load() 

+

71 

+

72 def __iter__(self) -> Iterator[T]: 

+

73 if self.sort: 

+

74 return iter(sorted(self.load())) 

+

75 else: 

+

76 return iter(self.load()) 

+

77 

+

78 def __call__(self, parser, namespace, values, option_string=None): 

+

79 setattr(namespace, self.dest, values) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___init___py.html b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___init___py.html new file mode 100644 index 0000000000..3994304692 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\manager\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\manager\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___main___py.html b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___main___py.html new file mode 100644 index 0000000000..8df109f41a --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39___main___py.html @@ -0,0 +1,160 @@ + + + + + Coverage for httpie\manager\__main__.py: 86% + + + + + +
+
+

+ Coverage for httpie\manager\__main__.py: + 86% +

+ +

+ 33 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import sys 

+

3 

+

4from typing import List, Union 

+

5 

+

6from httpie.context import Environment 

+

7from httpie.status import ExitStatus 

+

8from httpie.manager.cli import parser 

+

9from httpie.manager.core import MSG_COMMAND_CONFUSION, program as main_program 

+

10 

+

11 

+

12def is_http_command(args: List[Union[str, bytes]], env: Environment) -> bool: 

+

13 """Check whether http/https parser can parse the arguments.""" 

+

14 

+

15 from httpie.cli.definition import parser as http_parser 

+

16 from httpie.manager.cli import COMMANDS 

+

17 

+

18 # If the user already selected a top-level sub-command, never 

+

19 # show the http/https version. E.g httpie plugins pie.dev/post 

+

20 if len(args) >= 1 and args[0] in COMMANDS: 

+

21 return False 

+

22 

+

23 with env.as_silent(): 

+

24 try: 

+

25 http_parser.parse_args(env=env, args=args) 

+

26 except (Exception, SystemExit): 

+

27 return False 

+

28 else: 

+

29 return True 

+

30 

+

31 

+

32def main(args: List[Union[str, bytes]] = sys.argv, env: Environment = Environment()) -> ExitStatus: 

+

33 from httpie.core import raw_main 

+

34 

+

35 try: 

+

36 return raw_main( 

+

37 parser=parser, 

+

38 main_program=main_program, 

+

39 args=args, 

+

40 env=env, 

+

41 use_default_options=False, 

+

42 ) 

+

43 except argparse.ArgumentError: 

+

44 program_args = args[1:] 

+

45 if is_http_command(program_args, env): 

+

46 env.stderr.write(MSG_COMMAND_CONFUSION.format(args=' '.join(program_args)) + "\n") 

+

47 

+

48 return ExitStatus.ERROR 

+

49 

+

50 

+

51def program(): 

+

52 try: 

+

53 exit_status = main() 

+

54 except KeyboardInterrupt: 

+

55 exit_status = ExitStatus.ERROR_CTRL_C 

+

56 

+

57 return exit_status 

+

58 

+

59 

+

60if __name__ == '__main__': # pragma: nocover 

+

61 sys.exit(program()) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_cli_py.html b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_cli_py.html new file mode 100644 index 0000000000..a56d125269 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_cli_py.html @@ -0,0 +1,280 @@ + + + + + Coverage for httpie\manager\cli.py: 100% + + + + + +
+
+

+ Coverage for httpie\manager\cli.py: + 100% +

+ +

+ 39 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from textwrap import dedent 

+

2from httpie.cli.argparser import HTTPieManagerArgumentParser 

+

3from httpie.cli.options import Qualifiers, ARGPARSE_QUALIFIER_MAP, map_qualifiers, parser_to_parser_spec 

+

4from httpie import __version__ 

+

5 

+

6CLI_SESSION_UPGRADE_FLAGS = [ 

+

7 { 

+

8 'flags': ['--bind-cookies'], 

+

9 'action': 'store_true', 

+

10 'default': False, 

+

11 'help': 'Bind domainless cookies to the host that session belongs.' 

+

12 } 

+

13] 

+

14 

+

15COMMANDS = { 

+

16 'cli': { 

+

17 'help': 'Manage HTTPie for Terminal', 

+

18 'export-args': [ 

+

19 'Export available options for the CLI', 

+

20 { 

+

21 'flags': ['-f', '--format'], 

+

22 'choices': ['json'], 

+

23 'help': 'Format to export in.', 

+

24 'default': 'json' 

+

25 } 

+

26 ], 

+

27 'check-updates': [ 

+

28 'Check for updates' 

+

29 ], 

+

30 'sessions': { 

+

31 'help': 'Manage HTTPie sessions', 

+

32 'upgrade': [ 

+

33 'Upgrade the given HTTPie session with the latest ' 

+

34 'layout. A list of changes between different session versions ' 

+

35 'can be found in the official documentation.', 

+

36 { 

+

37 'dest': 'hostname', 

+

38 'metavar': 'HOSTNAME', 

+

39 'help': 'The host this session belongs.' 

+

40 }, 

+

41 { 

+

42 'dest': 'session', 

+

43 'metavar': 'SESSION_NAME_OR_PATH', 

+

44 'help': 'The name or the path for the session that will be upgraded.' 

+

45 }, 

+

46 *CLI_SESSION_UPGRADE_FLAGS 

+

47 ], 

+

48 'upgrade-all': [ 

+

49 'Upgrade all named sessions with the latest layout. A list of ' 

+

50 'changes between different session versions can be found in the official ' 

+

51 'documentation.', 

+

52 *CLI_SESSION_UPGRADE_FLAGS 

+

53 ], 

+

54 } 

+

55 } 

+

56} 

+

57 

+

58 

+

59COMMANDS['plugins'] = COMMANDS['cli']['plugins'] = { 

+

60 'help': 'Manage HTTPie plugins.', 

+

61 'install': [ 

+

62 'Install the given targets from PyPI ' 

+

63 'or from a local paths.', 

+

64 { 

+

65 'dest': 'targets', 

+

66 'metavar': 'TARGET', 

+

67 'nargs': Qualifiers.ONE_OR_MORE, 

+

68 'help': 'targets to install' 

+

69 } 

+

70 ], 

+

71 'upgrade': [ 

+

72 'Upgrade the given plugins', 

+

73 { 

+

74 'dest': 'targets', 

+

75 'metavar': 'TARGET', 

+

76 'nargs': Qualifiers.ONE_OR_MORE, 

+

77 'help': 'targets to upgrade' 

+

78 } 

+

79 ], 

+

80 'uninstall': [ 

+

81 'Uninstall the given HTTPie plugins.', 

+

82 { 

+

83 'dest': 'targets', 

+

84 'metavar': 'TARGET', 

+

85 'nargs': Qualifiers.ONE_OR_MORE, 

+

86 'help': 'targets to install' 

+

87 } 

+

88 ], 

+

89 'list': [ 

+

90 'List all installed HTTPie plugins.' 

+

91 ], 

+

92} 

+

93 

+

94 

+

95def missing_subcommand(*args) -> str: 

+

96 base = COMMANDS 

+

97 for arg in args: 

+

98 base = base[arg] 

+

99 

+

100 assert isinstance(base, dict) 

+

101 subcommands = ', '.join(map(repr, base.keys())) 

+

102 return f'Please specify one of these: {subcommands}' 

+

103 

+

104 

+

105def generate_subparsers(root, parent_parser, definitions, spec): 

+

106 action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action']) 

+

107 actions = parent_parser.add_subparsers( 

+

108 dest=action_dest 

+

109 ) 

+

110 for command, properties in definitions.items(): 

+

111 is_subparser = isinstance(properties, dict) 

+

112 properties = properties.copy() 

+

113 

+

114 descr = properties.pop('help', None) if is_subparser else properties.pop(0) 

+

115 command_parser = actions.add_parser(command, description=descr) 

+

116 command_parser.root = root 

+

117 if is_subparser: 

+

118 generate_subparsers(root, command_parser, properties, spec) 

+

119 continue 

+

120 

+

121 group = spec.add_group(parent_parser.prog + ' ' + command, description=descr) 

+

122 for argument in properties: 

+

123 argument = argument.copy() 

+

124 flags = argument.pop('flags', []) 

+

125 command_parser.add_argument(*flags, **map_qualifiers(argument, ARGPARSE_QUALIFIER_MAP)) 

+

126 group.add_argument(*flags, **argument) 

+

127 

+

128 

+

129parser = HTTPieManagerArgumentParser( 

+

130 prog='httpie', 

+

131 description=dedent( 

+

132 ''' 

+

133 Managing interface for the HTTPie itself. <https://httpie.io/docs#manager> 

+

134 

+

135 Be aware that you might be looking for http/https commands for sending 

+

136 HTTP requests. This command is only available for managing the HTTTPie 

+

137 plugins and the configuration around it. 

+

138 ''' 

+

139 ), 

+

140) 

+

141 

+

142parser.add_argument( 

+

143 '--debug', 

+

144 action='store_true', 

+

145 default=False, 

+

146 help=''' 

+

147 Prints the exception traceback should one occur, as well as other 

+

148 information useful for debugging HTTPie itself and for reporting bugs. 

+

149 

+

150 ''' 

+

151) 

+

152 

+

153parser.add_argument( 

+

154 '--traceback', 

+

155 action='store_true', 

+

156 default=False, 

+

157 help=''' 

+

158 Prints the exception traceback should one occur. 

+

159 

+

160 ''' 

+

161) 

+

162 

+

163parser.add_argument( 

+

164 '--version', 

+

165 action='version', 

+

166 version=__version__, 

+

167 help=''' 

+

168 Show version and exit. 

+

169 

+

170 ''' 

+

171) 

+

172 

+

173man_page_hint = ''' 

+

174If you are looking for the man pages of http/https commands, try one of the following: 

+

175 $ man http 

+

176 $ man https 

+

177 

+

178''' 

+

179 

+

180options = parser_to_parser_spec(parser, man_page_hint=man_page_hint, source_file=__file__) 

+

181generate_subparsers(parser, parser, COMMANDS, options) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_compat_py.html b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_compat_py.html new file mode 100644 index 0000000000..1a073167c8 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_compat_py.html @@ -0,0 +1,167 @@ + + + + + Coverage for httpie\manager\compat.py: 53% + + + + + +
+
+

+ Coverage for httpie\manager\compat.py: + 53% +

+ +

+ 35 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import sys 

+

2import shutil 

+

3import subprocess 

+

4 

+

5from contextlib import suppress 

+

6from typing import List, Optional 

+

7from httpie.compat import is_frozen 

+

8 

+

9 

+

10class PipError(Exception): 

+

11 """An exception that occurs when pip exits with an error status code.""" 

+

12 

+

13 def __init__(self, stdout, stderr): 

+

14 self.stdout = stdout 

+

15 self.stderr = stderr 

+

16 

+

17 

+

18def _discover_system_pip() -> List[str]: 

+

19 # When we are running inside of a frozen binary, we need the system 

+

20 # pip to install plugins since there is no way for us to execute any 

+

21 # code outside of the HTTPie. 

+

22 # 

+

23 # We explicitly depend on system pip, so the SystemError should not 

+

24 # be executed (except for broken installations). 

+

25 def _check_pip_version(pip_location: Optional[str]) -> bool: 

+

26 if not pip_location: 

+

27 return False 

+

28 

+

29 with suppress(subprocess.CalledProcessError): 

+

30 stdout = subprocess.check_output([pip_location, "--version"], text=True) 

+

31 return "python 3" in stdout 

+

32 

+

33 targets = [ 

+

34 "pip", 

+

35 "pip3" 

+

36 ] 

+

37 for target in targets: 

+

38 pip_location = shutil.which(target) 

+

39 if _check_pip_version(pip_location): 

+

40 return pip_location 

+

41 

+

42 raise SystemError("Couldn't find 'pip' executable. Please ensure that pip in your system is available.") 

+

43 

+

44 

+

45def _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes: 

+

46 

+

47 cmd = [*pip_executable, *args] 

+

48 try: 

+

49 process = subprocess.run( 

+

50 cmd, 

+

51 check=True, 

+

52 shell=False, 

+

53 stdout=subprocess.PIPE, 

+

54 stderr=subprocess.PIPE 

+

55 ) 

+

56 except subprocess.CalledProcessError as error: 

+

57 raise PipError(error.stdout, error.stderr) from error 

+

58 else: 

+

59 return process.stdout 

+

60 

+

61 

+

62def run_pip(args: List[str]) -> bytes: 

+

63 if is_frozen: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

+

64 pip_executable = [_discover_system_pip()] 

+

65 else: 

+

66 pip_executable = [sys.executable, '-m', 'pip'] 

+

67 

+

68 return _run_pip_subprocess(pip_executable, args) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_core_py.html b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_core_py.html new file mode 100644 index 0000000000..e600a0ca18 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7355ca5fa883be39_core_py.html @@ -0,0 +1,141 @@ + + + + + Coverage for httpie\manager\core.py: 93% + + + + + +
+
+

+ Coverage for httpie\manager\core.py: + 93% +

+ +

+ 20 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from typing import Optional 

+

3 

+

4from httpie.context import Environment 

+

5from httpie.status import ExitStatus 

+

6from httpie.manager.cli import missing_subcommand, parser 

+

7from httpie.manager.tasks import CLI_TASKS 

+

8 

+

9MSG_COMMAND_CONFUSION = '''\ 

+

10This command is only for managing HTTPie plugins. 

+

11To send a request, please use the http/https commands: 

+

12 

+

13 $ http {args} 

+

14 

+

15 $ https {args} 

+

16''' 

+

17 

+

18# noinspection PyStringFormat 

+

19MSG_NAKED_INVOCATION = f'''\ 

+

20{missing_subcommand()} 

+

21 

+

22{MSG_COMMAND_CONFUSION} 

+

23'''.rstrip("\n").format(args='POST pie.dev/post hello=world') 

+

24 

+

25 

+

26def dispatch_cli_task(env: Environment, action: Optional[str], args: argparse.Namespace) -> ExitStatus: 

+

27 if action is None: 

+

28 parser.error(missing_subcommand('cli')) 

+

29 

+

30 return CLI_TASKS[action](env, args) 

+

31 

+

32 

+

33def program(args: argparse.Namespace, env: Environment) -> ExitStatus: 

+

34 if args.action is None: 

+

35 parser.error(MSG_NAKED_INVOCATION) 

+

36 

+

37 if args.action == 'plugins': 

+

38 return dispatch_cli_task(env, args.action, args) 

+

39 elif args.action == 'cli': 39 ↛ 42line 39 didn't jump to line 42 because the condition on line 39 was always true

+

40 return dispatch_cli_task(env, args.cli_action, args) 

+

41 

+

42 return ExitStatus.SUCCESS 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a___init___py.html b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a___init___py.html new file mode 100644 index 0000000000..408bf9e250 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a___init___py.html @@ -0,0 +1,110 @@ + + + + + Coverage for httpie\manager\tasks\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\manager\tasks\__init__.py: + 100% +

+ +

+ 5 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from httpie.manager.tasks.sessions import cli_sessions 

+

2from httpie.manager.tasks.export_args import cli_export_args 

+

3from httpie.manager.tasks.plugins import cli_plugins 

+

4from httpie.manager.tasks.check_updates import cli_check_updates 

+

5 

+

6CLI_TASKS = { 

+

7 'sessions': cli_sessions, 

+

8 'export-args': cli_export_args, 

+

9 'plugins': cli_plugins, 

+

10 'check-updates': cli_check_updates 

+

11} 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_check_updates_py.html b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_check_updates_py.html new file mode 100644 index 0000000000..a975fc548a --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_check_updates_py.html @@ -0,0 +1,109 @@ + + + + + Coverage for httpie\manager\tasks\check_updates.py: 100% + + + + + +
+
+

+ Coverage for httpie\manager\tasks\check_updates.py: + 100% +

+ +

+ 8 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from httpie.context import Environment 

+

3from httpie.status import ExitStatus 

+

4from httpie.internal.update_warnings import fetch_updates, get_update_status 

+

5 

+

6 

+

7def cli_check_updates(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

8 fetch_updates(env, lazy=False) 

+

9 env.stdout.write(get_update_status(env)) 

+

10 return ExitStatus.SUCCESS 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_export_args_py.html b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_export_args_py.html new file mode 100644 index 0000000000..3f2d0d4641 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_export_args_py.html @@ -0,0 +1,126 @@ + + + + + Coverage for httpie\manager\tasks\export_args.py: 88% + + + + + +
+
+

+ Coverage for httpie\manager\tasks\export_args.py: + 88% +

+ +

+ 14 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import json 

+

3 

+

4from httpie.cli.definition import options 

+

5from httpie.cli.options import to_data 

+

6from httpie.output.writer import write_raw_data 

+

7from httpie.status import ExitStatus 

+

8from httpie.context import Environment 

+

9 

+

10 

+

11FORMAT_TO_CONTENT_TYPE = { 

+

12 'json': 'application/json' 

+

13} 

+

14 

+

15 

+

16def cli_export_args(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

17 if args.format == 'json': 17 ↛ 20line 17 didn't jump to line 20 because the condition on line 17 was always true

+

18 data = json.dumps(to_data(options)) 

+

19 else: 

+

20 raise NotImplementedError(f'Unexpected format value: {args.format}') 

+

21 

+

22 write_raw_data( 

+

23 env, 

+

24 data, 

+

25 stream_kwargs={'mime_overwrite': FORMAT_TO_CONTENT_TYPE[args.format]}, 

+

26 ) 

+

27 return ExitStatus.SUCCESS 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_plugins_py.html b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_plugins_py.html new file mode 100644 index 0000000000..0821d8f45c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_plugins_py.html @@ -0,0 +1,347 @@ + + + + + Coverage for httpie\manager\tasks\plugins.py: 72% + + + + + +
+
+

+ Coverage for httpie\manager\tasks\plugins.py: + 72% +

+ +

+ 153 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2import os 

+

3import textwrap 

+

4import re 

+

5import shutil 

+

6from collections import defaultdict 

+

7from contextlib import suppress 

+

8from pathlib import Path 

+

9from typing import List, Optional, Tuple 

+

10 

+

11from httpie.manager.compat import PipError, run_pip 

+

12from httpie.manager.cli import parser, missing_subcommand 

+

13from httpie.compat import get_dist_name, importlib_metadata 

+

14from httpie.context import Environment 

+

15from httpie.status import ExitStatus 

+

16from httpie.utils import get_site_paths 

+

17 

+

18PEP_503 = re.compile(r"[-_.]+") 

+

19 

+

20 

+

21class PluginInstaller: 

+

22 

+

23 def __init__(self, env: Environment, debug: bool = False) -> None: 

+

24 self.env = env 

+

25 self.dir = env.config.plugins_dir 

+

26 self.debug = debug 

+

27 

+

28 self.setup_plugins_dir() 

+

29 

+

30 def setup_plugins_dir(self) -> None: 

+

31 try: 

+

32 self.dir.mkdir( 

+

33 exist_ok=True, 

+

34 parents=True 

+

35 ) 

+

36 except OSError: 

+

37 self.env.stderr.write( 

+

38 f'Couldn\'t create "{self.dir!s}"' 

+

39 ' directory for plugin installation.' 

+

40 ' Please re-check the permissions for that directory,' 

+

41 ' and if needed, allow write-access.' 

+

42 ) 

+

43 raise 

+

44 

+

45 def fail( 

+

46 self, 

+

47 command: str, 

+

48 target: Optional[str] = None, 

+

49 reason: Optional[str] = None 

+

50 ) -> ExitStatus: 

+

51 message = f'Can\'t {command}' 

+

52 if target: 52 ↛ 54line 52 didn't jump to line 54 because the condition on line 52 was always true

+

53 message += f' {target!r}' 

+

54 if reason: 

+

55 message += f': {reason}' 

+

56 

+

57 self.env.stderr.write(message + '\n') 

+

58 return ExitStatus.ERROR 

+

59 

+

60 def _install(self, targets: List[str], mode='install') -> Tuple[ 

+

61 bytes, ExitStatus 

+

62 ]: 

+

63 pip_args = [ 

+

64 'install', 

+

65 '--prefer-binary', 

+

66 f'--prefix={self.dir}', 

+

67 '--no-warn-script-location', 

+

68 ] 

+

69 if mode == 'upgrade': 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true

+

70 pip_args.append('--upgrade') 

+

71 pip_args.extend(targets) 

+

72 

+

73 try: 

+

74 stdout = run_pip(pip_args) 

+

75 except PipError as pip_error: 

+

76 error = pip_error 

+

77 stdout = pip_error.stdout 

+

78 else: 

+

79 error = None 

+

80 

+

81 self.env.stdout.write(stdout.decode()) 

+

82 

+

83 if error: 

+

84 reason = None 

+

85 if error.stderr: 85 ↛ 98line 85 didn't jump to line 98 because the condition on line 85 was always true

+

86 stderr = error.stderr.decode() 

+

87 

+

88 if self.debug: 88 ↛ 93line 88 didn't jump to line 93 because the condition on line 88 was always true

+

89 self.env.stderr.write('Command failed: ') 

+

90 self.env.stderr.write('pip ' + ' '.join(pip_args) + '\n') 

+

91 self.env.stderr.write(textwrap.indent(' ', stderr)) 

+

92 

+

93 last_line = stderr.strip().splitlines()[-1] 

+

94 severity, _, message = last_line.partition(': ') 

+

95 if severity == 'ERROR': 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

+

96 reason = message 

+

97 

+

98 stdout = error.stdout 

+

99 exit_status = self.fail(mode, ', '.join(targets), reason) 

+

100 else: 

+

101 exit_status = ExitStatus.SUCCESS 

+

102 

+

103 return stdout, exit_status 

+

104 

+

105 def install(self, targets: List[str]) -> ExitStatus: 

+

106 self.env.stdout.write(f"Installing {', '.join(targets)}...\n") 

+

107 self.env.stdout.flush() 

+

108 _, exit_status = self._install(targets) 

+

109 return exit_status 

+

110 

+

111 def _clear_metadata(self, targets: List[str]) -> None: 

+

112 # Due to an outstanding pip problem[0], we have to get rid of 

+

113 # existing metadata for old versions manually. 

+

114 # [0]: https://github.com/pypa/pip/issues/10727 

+

115 result_deps = defaultdict(list) 

+

116 for site_dir in get_site_paths(self.dir): 

+

117 for child in site_dir.iterdir(): 

+

118 if child.suffix in {'.dist-info', '.egg-info'}: 

+

119 name, _, version = child.stem.rpartition('-') 

+

120 result_deps[name].append((version, child)) 

+

121 

+

122 for target in targets: 

+

123 name, _, version = target.rpartition('-') 

+

124 name = PEP_503.sub("-", name).lower().replace('-', '_') 

+

125 if name not in result_deps: 

+

126 continue 

+

127 

+

128 for result_version, meta_path in result_deps[name]: 

+

129 if version != result_version: 

+

130 shutil.rmtree(meta_path) 

+

131 

+

132 def upgrade(self, targets: List[str]) -> ExitStatus: 

+

133 self.env.stdout.write(f"Upgrading {', '.join(targets)}...\n") 

+

134 self.env.stdout.flush() 

+

135 

+

136 raw_stdout, exit_status = self._install( 

+

137 targets, 

+

138 mode='upgrade' 

+

139 ) 

+

140 if not raw_stdout: 

+

141 return exit_status 

+

142 

+

143 stdout = raw_stdout.decode() 

+

144 installation_line = stdout.splitlines()[-1] 

+

145 if installation_line.startswith('Successfully installed'): 

+

146 self._clear_metadata(installation_line.split()[2:]) 

+

147 

+

148 def _uninstall(self, target: str) -> Optional[ExitStatus]: 

+

149 try: 

+

150 distribution = importlib_metadata.distribution(target) 

+

151 except importlib_metadata.PackageNotFoundError: 

+

152 return self.fail('uninstall', target, 'package is not installed') 

+

153 

+

154 base_dir = Path(distribution.locate_file('.')).resolve() 

+

155 if self.dir not in base_dir.parents: 155 ↛ 159line 155 didn't jump to line 159 because the condition on line 155 was never true

+

156 # If the package is installed somewhere else (e.g on the site packages 

+

157 # of the real python interpreter), than that means this package is not 

+

158 # installed through us. 

+

159 return self.fail('uninstall', target, 

+

160 'package is not installed through httpie plugins' 

+

161 ' interface') 

+

162 

+

163 files = distribution.files 

+

164 if files is None: 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true

+

165 return self.fail('uninstall', target, 'couldn\'t locate the package') 

+

166 

+

167 # TODO: Consider handling failures here (e.g if it fails, 

+

168 # just revert the operation and leave the site-packages 

+

169 # in a proper shape). 

+

170 for file in files: 

+

171 with suppress(FileNotFoundError): 

+

172 os.unlink(distribution.locate_file(file)) 

+

173 

+

174 metadata_path = getattr(distribution, '_path', None) 

+

175 if ( 175 ↛ 182line 175 didn't jump to line 182 because the condition on line 175 was always true

+

176 metadata_path 

+

177 and metadata_path.exists() 

+

178 and not any(metadata_path.iterdir()) 

+

179 ): 

+

180 metadata_path.rmdir() 

+

181 

+

182 self.env.stdout.write(f'Successfully uninstalled {target}\n') 

+

183 

+

184 def uninstall(self, targets: List[str]) -> ExitStatus: 

+

185 # Unfortunately uninstall doesn't work with custom pip schemes. See: 

+

186 # - https://github.com/pypa/pip/issues/5595 

+

187 # - https://github.com/pypa/pip/issues/4575 

+

188 # so we have to implement our own uninstalling logic. Which works 

+

189 # on top of the importlib_metadata. 

+

190 

+

191 exit_code = ExitStatus.SUCCESS 

+

192 for target in targets: 

+

193 exit_code |= self._uninstall(target) or ExitStatus.SUCCESS 

+

194 return ExitStatus(exit_code) 

+

195 

+

196 def list(self) -> None: 

+

197 from httpie.plugins.registry import plugin_manager 

+

198 

+

199 known_plugins = defaultdict(list) 

+

200 

+

201 for entry_point in plugin_manager.iter_entry_points(self.dir): 

+

202 ep_info = (entry_point.group, entry_point.name) 

+

203 ep_name = get_dist_name(entry_point) or entry_point.module 

+

204 known_plugins[ep_name].append(ep_info) 

+

205 

+

206 for plugin, entry_points in known_plugins.items(): 

+

207 self.env.stdout.write(plugin) 

+

208 

+

209 version = importlib_metadata.version(plugin) 

+

210 if version is not None: 210 ↛ 212line 210 didn't jump to line 212 because the condition on line 210 was always true

+

211 self.env.stdout.write(f' ({version})') 

+

212 self.env.stdout.write('\n') 

+

213 

+

214 for group, entry_point in sorted(entry_points): 

+

215 self.env.stdout.write(f' {entry_point} ({group})\n') 

+

216 

+

217 def run( 

+

218 self, 

+

219 action: Optional[str], 

+

220 args: argparse.Namespace, 

+

221 ) -> ExitStatus: 

+

222 from httpie.plugins.manager import enable_plugins 

+

223 

+

224 if action is None: 

+

225 parser.error(missing_subcommand('plugins')) 

+

226 

+

227 with enable_plugins(self.dir): 

+

228 if action == 'install': 

+

229 status = self.install(args.targets) 

+

230 elif action == 'upgrade': 230 ↛ 231line 230 didn't jump to line 231 because the condition on line 230 was never true

+

231 status = self.upgrade(args.targets) 

+

232 elif action == 'uninstall': 

+

233 status = self.uninstall(args.targets) 

+

234 elif action == 'list': 234 ↛ 237line 234 didn't jump to line 237

+

235 status = self.list() 

+

236 

+

237 return status or ExitStatus.SUCCESS 

+

238 

+

239 

+

240def cli_plugins(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

241 plugins = PluginInstaller(env, debug=args.debug) 

+

242 

+

243 try: 

+

244 action = args.cli_plugins_action 

+

245 except AttributeError: 

+

246 action = args.plugins_action 

+

247 

+

248 return plugins.run(action, args) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_sessions_py.html b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_sessions_py.html new file mode 100644 index 0000000000..8ae4cb0a04 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_7d042ac8ba564a7a_sessions_py.html @@ -0,0 +1,185 @@ + + + + + Coverage for httpie\manager\tasks\sessions.py: 93% + + + + + +
+
+

+ Coverage for httpie\manager\tasks\sessions.py: + 93% +

+ +

+ 43 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2 

+

3from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session 

+

4from httpie.status import ExitStatus 

+

5from httpie.context import Environment 

+

6from httpie.legacy import v3_1_0_session_cookie_format, v3_2_0_session_header_format 

+

7from httpie.manager.cli import missing_subcommand, parser 

+

8from httpie.utils import is_version_greater 

+

9 

+

10 

+

11FIXERS_TO_VERSIONS = { 

+

12 '3.1.0': v3_1_0_session_cookie_format.fix_layout, 

+

13 '3.2.0': v3_2_0_session_header_format.fix_layout, 

+

14} 

+

15 

+

16 

+

17def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

18 action = args.cli_sessions_action 

+

19 if action is None: 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true

+

20 parser.error(missing_subcommand('cli', 'sessions')) 

+

21 

+

22 if action == 'upgrade': 

+

23 return cli_upgrade_session(env, args) 

+

24 elif action == 'upgrade-all': 24 ↛ 27line 24 didn't jump to line 27 because the condition on line 24 was always true

+

25 return cli_upgrade_all_sessions(env, args) 

+

26 else: 

+

27 raise ValueError(f'Unexpected action: {action}') 

+

28 

+

29 

+

30def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str): 

+

31 session = get_httpie_session( 

+

32 env=env, 

+

33 config_dir=env.config.directory, 

+

34 session_name=session_name, 

+

35 host=hostname, 

+

36 url=hostname, 

+

37 suppress_legacy_warnings=True 

+

38 ) 

+

39 

+

40 session_name = session.path.stem 

+

41 if session.is_new(): 

+

42 env.log_error(f'{session_name!r} @ {hostname!r} does not exist.') 

+

43 return ExitStatus.ERROR 

+

44 

+

45 fixers = [ 

+

46 fixer 

+

47 for version, fixer in FIXERS_TO_VERSIONS.items() 

+

48 if is_version_greater(version, session.version) 

+

49 ] 

+

50 

+

51 if len(fixers) == 0: 

+

52 env.stdout.write(f'{session_name!r} @ {hostname!r} is already up to date.\n') 

+

53 return ExitStatus.SUCCESS 

+

54 

+

55 for fixer in fixers: 

+

56 fixer(session, hostname, args) 

+

57 

+

58 session.save(bump_version=True) 

+

59 env.stdout.write(f'Upgraded {session_name!r} @ {hostname!r} to v{session.version}\n') 

+

60 return ExitStatus.SUCCESS 

+

61 

+

62 

+

63def cli_upgrade_session(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

64 return upgrade_session( 

+

65 env, 

+

66 args=args, 

+

67 hostname=args.hostname, 

+

68 session_name=args.session 

+

69 ) 

+

70 

+

71 

+

72def cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus: 

+

73 session_dir_path = env.config_dir / SESSIONS_DIR_NAME 

+

74 

+

75 status = ExitStatus.SUCCESS 

+

76 for host_path in session_dir_path.iterdir(): 

+

77 hostname = host_path.name 

+

78 for session_path in host_path.glob("*.json"): 

+

79 session_name = session_path.stem 

+

80 status |= upgrade_session( 

+

81 env, 

+

82 args=args, 

+

83 hostname=hostname, 

+

84 session_name=session_name 

+

85 ) 

+

86 return status 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191___init___py.html b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191___init___py.html new file mode 100644 index 0000000000..8d364290d3 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\output\lexers\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\lexers\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_common_py.html b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_common_py.html new file mode 100644 index 0000000000..2025612108 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_common_py.html @@ -0,0 +1,111 @@ + + + + + Coverage for httpie\output\lexers\common.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\lexers\common.py: + 100% +

+ +

+ 4 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1def precise(lexer, precise_token, parent_token): 

+

2 # Due to a pygments bug*, custom tokens will look bad 

+

3 # on outside styles. Until it is fixed on upstream, we'll 

+

4 # convey whether the client is using pie style or not 

+

5 # through precise option and return more precise tokens 

+

6 # depending on it's value. 

+

7 # 

+

8 # [0]: https://github.com/pygments/pygments/issues/1986 

+

9 if precise_token is None or not lexer.options.get("precise"): 

+

10 return parent_token 

+

11 else: 

+

12 return precise_token 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_http_py.html b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_http_py.html new file mode 100644 index 0000000000..25dd0b4537 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_http_py.html @@ -0,0 +1,196 @@ + + + + + Coverage for httpie\output\lexers\http.py: 83% + + + + + +
+
+

+ Coverage for httpie\output\lexers\http.py: + 83% +

+ +

+ 22 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import re 

+

2import pygments 

+

3from httpie.output.lexers.common import precise 

+

4 

+

5RE_STATUS_LINE = re.compile(r'(\d{3})( +)?(.+)?') 

+

6 

+

7STATUS_TYPES = { 

+

8 '1': pygments.token.Number.HTTP.INFO, 

+

9 '2': pygments.token.Number.HTTP.OK, 

+

10 '3': pygments.token.Number.HTTP.REDIRECT, 

+

11 '4': pygments.token.Number.HTTP.CLIENT_ERR, 

+

12 '5': pygments.token.Number.HTTP.SERVER_ERR, 

+

13} 

+

14 

+

15RESPONSE_TYPES = { 

+

16 'GET': pygments.token.Name.Function.HTTP.GET, 

+

17 'HEAD': pygments.token.Name.Function.HTTP.HEAD, 

+

18 'POST': pygments.token.Name.Function.HTTP.POST, 

+

19 'PUT': pygments.token.Name.Function.HTTP.PUT, 

+

20 'PATCH': pygments.token.Name.Function.HTTP.PATCH, 

+

21 'DELETE': pygments.token.Name.Function.HTTP.DELETE, 

+

22} 

+

23 

+

24 

+

25def http_response_type(lexer, match, ctx): 

+

26 status_match = RE_STATUS_LINE.match(match.group()) 

+

27 if status_match is None: 27 ↛ 28line 27 didn't jump to line 28 because the condition on line 27 was never true

+

28 return None 

+

29 

+

30 status_code, text, reason = status_match.groups() 

+

31 status_type = precise( 

+

32 lexer, 

+

33 STATUS_TYPES.get(status_code[0]), 

+

34 pygments.token.Number 

+

35 ) 

+

36 

+

37 groups = pygments.lexer.bygroups( 

+

38 status_type, 

+

39 pygments.token.Text, 

+

40 status_type 

+

41 ) 

+

42 yield from groups(lexer, status_match, ctx) 

+

43 

+

44 

+

45def request_method(lexer, match, ctx): 

+

46 response_type = precise( 

+

47 lexer, 

+

48 RESPONSE_TYPES.get(match.group()), 

+

49 pygments.token.Name.Function 

+

50 ) 

+

51 yield match.start(), response_type, match.group() 

+

52 

+

53 

+

54class SimplifiedHTTPLexer(pygments.lexer.RegexLexer): 

+

55 """Simplified HTTP lexer for Pygments. 

+

56 

+

57 It only operates on headers and provides a stronger contrast between 

+

58 their names and values than the original one bundled with Pygments 

+

59 (:class:`pygments.lexers.text import HttpLexer`), especially when 

+

60 Solarized color scheme is used. 

+

61 

+

62 """ 

+

63 name = 'HTTP' 

+

64 aliases = ['http'] 

+

65 filenames = ['*.http'] 

+

66 tokens = { 

+

67 'root': [ 

+

68 # Request-Line 

+

69 (r'([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\d+\.\d+)', 

+

70 pygments.lexer.bygroups( 

+

71 request_method, 

+

72 pygments.token.Text, 

+

73 pygments.token.Name.Namespace, 

+

74 pygments.token.Text, 

+

75 pygments.token.Keyword.Reserved, 

+

76 pygments.token.Operator, 

+

77 pygments.token.Number 

+

78 )), 

+

79 # Response Status-Line 

+

80 (r'(HTTP)(/)(\d+\.\d+)( +)(.+)', 

+

81 pygments.lexer.bygroups( 

+

82 pygments.token.Keyword.Reserved, # 'HTTP' 

+

83 pygments.token.Operator, # '/' 

+

84 pygments.token.Number, # Version 

+

85 pygments.token.Text, 

+

86 http_response_type, # Status code and Reason 

+

87 )), 

+

88 # Header 

+

89 (r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups( 

+

90 pygments.token.Name.Attribute, # Name 

+

91 pygments.token.Text, 

+

92 pygments.token.Operator, # Colon 

+

93 pygments.token.Text, 

+

94 pygments.token.String # Value 

+

95 )) 

+

96 ] 

+

97 } 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_json_py.html b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_json_py.html new file mode 100644 index 0000000000..e93b7863a3 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_json_py.html @@ -0,0 +1,130 @@ + + + + + Coverage for httpie\output\lexers\json.py: 100% + + + + + +
+
+

+ Coverage for httpie\output\lexers\json.py: + 100% +

+ +

+ 10 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import re 

+

2 

+

3from pygments.lexer import bygroups, using, RegexLexer 

+

4from pygments.lexers.data import JsonLexer 

+

5from pygments.token import Token 

+

6 

+

7PREFIX_TOKEN = Token.Error 

+

8PREFIX_REGEX = r'[^{\["]+' 

+

9 

+

10 

+

11class EnhancedJsonLexer(RegexLexer): 

+

12 """ 

+

13 Enhanced JSON lexer for Pygments. 

+

14 

+

15 It adds support for eventual data prefixing the actual JSON body. 

+

16 

+

17 """ 

+

18 name = 'JSON' 

+

19 flags = re.IGNORECASE | re.DOTALL 

+

20 tokens = { 

+

21 'root': [ 

+

22 # Eventual non-JSON data prefix followed by actual JSON body. 

+

23 # FIX: data prefix + number (integer or float) is not correctly handled. 

+

24 ( 

+

25 fr'({PREFIX_REGEX})' + r'((?:[{\["]|true|false|null).+)', 

+

26 bygroups(PREFIX_TOKEN, using(JsonLexer)) 

+

27 ), 

+

28 # JSON body. 

+

29 (r'.+', using(JsonLexer)), 

+

30 ], 

+

31 } 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_metadata_py.html b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_metadata_py.html new file mode 100644 index 0000000000..7ad639c91c --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_a58f12c5cdcdd191_metadata_py.html @@ -0,0 +1,158 @@ + + + + + Coverage for httpie\output\lexers\metadata.py: 76% + + + + + +
+
+

+ Coverage for httpie\output\lexers\metadata.py: + 76% +

+ +

+ 17 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import pygments 

+

2 

+

3from httpie.models import ELAPSED_TIME_LABEL 

+

4from httpie.output.lexers.common import precise 

+

5 

+

6SPEED_TOKENS = { 

+

7 0.45: pygments.token.Number.SPEED.FAST, 

+

8 1.00: pygments.token.Number.SPEED.AVG, 

+

9 2.50: pygments.token.Number.SPEED.SLOW, 

+

10} 

+

11 

+

12 

+

13def speed_based_token(lexer, match, ctx): 

+

14 try: 

+

15 value = float(match.group()) 

+

16 except ValueError: 

+

17 return pygments.token.Number 

+

18 

+

19 for limit, token in SPEED_TOKENS.items(): 19 ↛ 23line 19 didn't jump to line 23 because the loop on line 19 didn't complete

+

20 if value <= limit: 20 ↛ 19line 20 didn't jump to line 19 because the condition on line 20 was always true

+

21 break 

+

22 else: 

+

23 token = pygments.token.Number.SPEED.VERY_SLOW 

+

24 

+

25 response_type = precise( 

+

26 lexer, 

+

27 token, 

+

28 pygments.token.Number 

+

29 ) 

+

30 yield match.start(), response_type, match.group() 

+

31 

+

32 

+

33class MetadataLexer(pygments.lexer.RegexLexer): 

+

34 """Simple HTTPie metadata lexer.""" 

+

35 

+

36 tokens = { 

+

37 'root': [ 

+

38 ( 

+

39 fr'({ELAPSED_TIME_LABEL})( *)(:)( *)(\d+\.\d+)(s)', pygments.lexer.bygroups( 

+

40 pygments.token.Name.Decorator, # Name 

+

41 pygments.token.Text, 

+

42 pygments.token.Operator, # Colon 

+

43 pygments.token.Text, 

+

44 speed_based_token, 

+

45 pygments.token.Name.Builtin # Value 

+

46 ) 

+

47 ), 

+

48 # Generic item 

+

49 ( 

+

50 r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups( 

+

51 pygments.token.Name.Decorator, # Name 

+

52 pygments.token.Text, 

+

53 pygments.token.Operator, # Colon 

+

54 pygments.token.Text, 

+

55 pygments.token.Text # Value 

+

56 ) 

+

57 ), 

+

58 ] 

+

59 } 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___build_channel___py.html b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___build_channel___py.html new file mode 100644 index 0000000000..5b051d3c90 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___build_channel___py.html @@ -0,0 +1,104 @@ + + + + + Coverage for httpie\internal\__build_channel__.py: 100% + + + + + +
+
+

+ Coverage for httpie\internal\__build_channel__.py: + 100% +

+ +

+ 1 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1# Represents the packaging method. This file should 

+

2# be overridden by every build system we support on 

+

3# the packaging step. 

+

4 

+

5BUILD_CHANNEL = 'unknown' 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___init___py.html b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___init___py.html new file mode 100644 index 0000000000..7f489313a2 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\internal\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\internal\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemon_runner_py.html b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemon_runner_py.html new file mode 100644 index 0000000000..467b53c691 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemon_runner_py.html @@ -0,0 +1,149 @@ + + + + + Coverage for httpie\internal\daemon_runner.py: 86% + + + + + +
+
+

+ Coverage for httpie\internal\daemon_runner.py: + 86% +

+ +

+ 28 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from contextlib import redirect_stderr, redirect_stdout 

+

3from typing import List 

+

4 

+

5from httpie.context import Environment 

+

6from httpie.internal.update_warnings import _fetch_updates, _get_suppress_context 

+

7from httpie.status import ExitStatus 

+

8 

+

9STATUS_FILE = '.httpie-test-daemon-status' 

+

10 

+

11 

+

12def _check_status(env): 

+

13 # This function is used only for the testing (test_update_warnings). 

+

14 # Since we don't want to trigger the fetch_updates (which would interact 

+

15 # with real world resources), we'll only trigger this pseudo task 

+

16 # and check whether the STATUS_FILE is created or not. 

+

17 import tempfile 

+

18 from pathlib import Path 

+

19 

+

20 status_file = Path(tempfile.gettempdir()) / STATUS_FILE 

+

21 status_file.touch() 

+

22 

+

23 

+

24DAEMONIZED_TASKS = { 

+

25 'check_status': _check_status, 

+

26 'fetch_updates': _fetch_updates, 

+

27} 

+

28 

+

29 

+

30def _parse_options(args: List[str]) -> argparse.Namespace: 

+

31 parser = argparse.ArgumentParser() 

+

32 parser.add_argument('task_id') 

+

33 parser.add_argument('--daemon', action='store_true') 

+

34 return parser.parse_known_args(args)[0] 

+

35 

+

36 

+

37def is_daemon_mode(args: List[str]) -> bool: 

+

38 return '--daemon' in args 

+

39 

+

40 

+

41def run_daemon_task(env: Environment, args: List[str]) -> ExitStatus: 

+

42 options = _parse_options(args) 

+

43 

+

44 assert options.daemon 

+

45 assert options.task_id in DAEMONIZED_TASKS 

+

46 with redirect_stdout(env.devnull), redirect_stderr(env.devnull): 

+

47 with _get_suppress_context(env): 

+

48 DAEMONIZED_TASKS[options.task_id](env) 

+

49 

+

50 return ExitStatus.SUCCESS 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemons_py.html b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemons_py.html new file mode 100644 index 0000000000..6a799a0696 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_daemons_py.html @@ -0,0 +1,220 @@ + + + + + Coverage for httpie\internal\daemons.py: 51% + + + + + +
+
+

+ Coverage for httpie\internal\daemons.py: + 51% +

+ +

+ 58 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2This module provides an interface to spawn a detached task to be 

+

3run with httpie.internal.daemon_runner on a separate process. It is 

+

4based on DVC's daemon system. 

+

5https://github.com/iterative/dvc/blob/main/dvc/daemon.py 

+

6""" 

+

7 

+

8import inspect 

+

9import os 

+

10import platform 

+

11import sys 

+

12import httpie.__main__ 

+

13from contextlib import suppress 

+

14from subprocess import Popen, DEVNULL 

+

15from typing import Dict, List 

+

16from httpie.compat import is_frozen, is_windows 

+

17 

+

18 

+

19ProcessContext = Dict[str, str] 

+

20 

+

21 

+

22def _start_process(cmd: List[str], **kwargs) -> Popen: 

+

23 prefix = [sys.executable] 

+

24 # If it is frozen, sys.executable points to the binary (http). 

+

25 # Otherwise it points to the python interpreter. 

+

26 if not is_frozen: 26 ↛ 29line 26 didn't jump to line 29 because the condition on line 26 was always true

+

27 main_entrypoint = httpie.__main__.__file__ 

+

28 prefix += [main_entrypoint] 

+

29 return Popen(prefix + cmd, close_fds=True, shell=False, stdout=DEVNULL, stderr=DEVNULL, **kwargs) 

+

30 

+

31 

+

32def _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None: 

+

33 from subprocess import ( 

+

34 CREATE_NEW_PROCESS_GROUP, 

+

35 CREATE_NO_WINDOW, 

+

36 STARTF_USESHOWWINDOW, 

+

37 STARTUPINFO, 

+

38 ) 

+

39 

+

40 # https://stackoverflow.com/a/7006424 

+

41 # https://bugs.python.org/issue41619 

+

42 creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW 

+

43 

+

44 startupinfo = STARTUPINFO() 

+

45 startupinfo.dwFlags |= STARTF_USESHOWWINDOW 

+

46 

+

47 _start_process( 

+

48 cmd, 

+

49 env=process_context, 

+

50 creationflags=creationflags, 

+

51 startupinfo=startupinfo, 

+

52 ) 

+

53 

+

54 

+

55def _spawn_posix(args: List[str], process_context: ProcessContext) -> None: 

+

56 """ 

+

57 Perform a double fork procedure* to detach from the parent 

+

58 process so that we don't block the user even if their original 

+

59 command's execution is done but the release fetcher is not. 

+

60 

+

61 [1]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03 

+

62 """ 

+

63 

+

64 from httpie.core import main 

+

65 

+

66 try: 

+

67 pid = os.fork() 

+

68 if pid > 0: 

+

69 return 

+

70 except OSError: 

+

71 os._exit(1) 

+

72 

+

73 os.setsid() 

+

74 

+

75 try: 

+

76 pid = os.fork() 

+

77 if pid > 0: 

+

78 os._exit(0) 

+

79 except OSError: 

+

80 os._exit(1) 

+

81 

+

82 # Close all standard inputs/outputs 

+

83 sys.stdin.close() 

+

84 sys.stdout.close() 

+

85 sys.stderr.close() 

+

86 

+

87 if platform.system() == 'Darwin': 

+

88 # Double-fork is not reliable on MacOS, so we'll use a subprocess 

+

89 # to ensure the task is isolated properly. 

+

90 process = _start_process(args, env=process_context) 

+

91 # Unlike windows, since we already completed the fork procedure 

+

92 # we can simply join the process and wait for it. 

+

93 process.communicate() 

+

94 else: 

+

95 os.environ.update(process_context) 

+

96 with suppress(BaseException): 

+

97 main(['http'] + args) 

+

98 

+

99 os._exit(0) 

+

100 

+

101 

+

102def _spawn(args: List[str], process_context: ProcessContext) -> None: 

+

103 """ 

+

104 Spawn a new process to run the given command. 

+

105 """ 

+

106 if is_windows: 106 ↛ 109line 106 didn't jump to line 109 because the condition on line 106 was always true

+

107 _spawn_windows(args, process_context) 

+

108 else: 

+

109 _spawn_posix(args, process_context) 

+

110 

+

111 

+

112def spawn_daemon(task: str) -> None: 

+

113 args = [task, '--daemon'] 

+

114 process_context = os.environ.copy() 

+

115 if not is_frozen: 115 ↛ 121line 115 didn't jump to line 121 because the condition on line 115 was always true

+

116 file_path = os.path.abspath(inspect.stack()[0][1]) 

+

117 process_context['PYTHONPATH'] = os.path.dirname( 

+

118 os.path.dirname(os.path.dirname(file_path)) 

+

119 ) 

+

120 

+

121 _spawn(args, process_context) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_update_warnings_py.html b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_update_warnings_py.html new file mode 100644 index 0000000000..5a7d5f39c7 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_c2aeaf7156838c3b_update_warnings_py.html @@ -0,0 +1,270 @@ + + + + + Coverage for httpie\internal\update_warnings.py: 91% + + + + + +
+
+

+ Coverage for httpie\internal\update_warnings.py: + 91% +

+ +

+ 96 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import json 

+

2from contextlib import nullcontext, suppress 

+

3from datetime import datetime, timedelta 

+

4from pathlib import Path 

+

5from typing import Any, Optional, Callable 

+

6 

+

7import requests 

+

8 

+

9import httpie 

+

10from httpie.context import Environment, LogLevel 

+

11from httpie.internal.__build_channel__ import BUILD_CHANNEL 

+

12from httpie.internal.daemons import spawn_daemon 

+

13from httpie.utils import is_version_greater, open_with_lockfile 

+

14 

+

15# Automatically updated package version index. 

+

16PACKAGE_INDEX_LINK = 'https://packages.httpie.io/latest.json' 

+

17 

+

18FETCH_INTERVAL = timedelta(weeks=2) 

+

19WARN_INTERVAL = timedelta(weeks=1) 

+

20 

+

21UPDATE_MESSAGE_FORMAT = """\ 

+

22A new HTTPie release ({last_released_version}) is available. 

+

23To see how you can update, please visit https://httpie.io/docs/cli/{installation_method} 

+

24""" 

+

25 

+

26ALREADY_UP_TO_DATE_MESSAGE = """\ 

+

27You are already up-to-date. 

+

28""" 

+

29 

+

30 

+

31def _read_data_error_free(file: Path) -> Any: 

+

32 # If the file is broken / non-existent, ignore it. 

+

33 try: 

+

34 with open(file) as stream: 

+

35 return json.load(stream) 

+

36 except (ValueError, OSError): 

+

37 return {} 

+

38 

+

39 

+

40def _fetch_updates(env: Environment) -> str: 

+

41 file = env.config.version_info_file 

+

42 data = _read_data_error_free(file) 

+

43 

+

44 response = requests.get(PACKAGE_INDEX_LINK, verify=False) 

+

45 response.raise_for_status() 

+

46 

+

47 data.setdefault('last_warned_date', None) 

+

48 data['last_fetched_date'] = datetime.now().isoformat() 

+

49 data['last_released_versions'] = response.json() 

+

50 

+

51 with open_with_lockfile(file, 'w') as stream: 

+

52 json.dump(data, stream) 

+

53 

+

54 

+

55def fetch_updates(env: Environment, lazy: bool = True): 

+

56 if lazy: 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true

+

57 spawn_daemon('fetch_updates') 

+

58 else: 

+

59 _fetch_updates(env) 

+

60 

+

61 

+

62def maybe_fetch_updates(env: Environment) -> None: 

+

63 if env.config.get('disable_update_warnings'): 

+

64 return None 

+

65 

+

66 data = _read_data_error_free(env.config.version_info_file) 

+

67 

+

68 if data: 

+

69 current_date = datetime.now() 

+

70 last_fetched_date = datetime.fromisoformat(data['last_fetched_date']) 

+

71 earliest_fetch_date = last_fetched_date + FETCH_INTERVAL 

+

72 if current_date < earliest_fetch_date: 72 ↛ 75line 72 didn't jump to line 75 because the condition on line 72 was always true

+

73 return None 

+

74 

+

75 fetch_updates(env) 

+

76 

+

77 

+

78def _get_suppress_context(env: Environment) -> Any: 

+

79 """Return a context manager that suppress 

+

80 all possible errors. 

+

81 

+

82 Note: if you have set the developer_mode=True in 

+

83 your config, then it will show all errors for easier 

+

84 debugging.""" 

+

85 if env.config.developer_mode: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true

+

86 return nullcontext() 

+

87 else: 

+

88 return suppress(BaseException) 

+

89 

+

90 

+

91def _update_checker( 

+

92 func: Callable[[Environment], None] 

+

93) -> Callable[[Environment], None]: 

+

94 """Control the execution of the update checker (suppress errors, trigger 

+

95 auto updates etc.)""" 

+

96 

+

97 def wrapper(env: Environment) -> None: 

+

98 with _get_suppress_context(env): 

+

99 func(env) 

+

100 

+

101 with _get_suppress_context(env): 

+

102 maybe_fetch_updates(env) 

+

103 

+

104 return wrapper 

+

105 

+

106 

+

107def _get_update_status(env: Environment) -> Optional[str]: 

+

108 """If there is a new update available, return the warning text. 

+

109 Otherwise just return None.""" 

+

110 file = env.config.version_info_file 

+

111 if not file.exists(): 

+

112 return None 

+

113 

+

114 with _get_suppress_context(env): 

+

115 # If the user quickly spawns multiple httpie processes 

+

116 # we don't want to end in a race. 

+

117 with open_with_lockfile(file) as stream: 

+

118 version_info = json.load(stream) 

+

119 

+

120 available_channels = version_info['last_released_versions'] 

+

121 if BUILD_CHANNEL not in available_channels: 

+

122 return None 

+

123 

+

124 current_version = httpie.__version__ 

+

125 last_released_version = available_channels[BUILD_CHANNEL] 

+

126 if not is_version_greater(last_released_version, current_version): 

+

127 return None 

+

128 

+

129 text = UPDATE_MESSAGE_FORMAT.format( 

+

130 last_released_version=last_released_version, 

+

131 installation_method=BUILD_CHANNEL, 

+

132 ) 

+

133 return text 

+

134 

+

135 

+

136def get_update_status(env: Environment) -> str: 

+

137 return _get_update_status(env) or ALREADY_UP_TO_DATE_MESSAGE 

+

138 

+

139 

+

140@_update_checker 

+

141def check_updates(env: Environment) -> None: 

+

142 if env.config.get('disable_update_warnings'): 

+

143 return None 

+

144 

+

145 file = env.config.version_info_file 

+

146 update_status = _get_update_status(env) 

+

147 

+

148 if not update_status: 

+

149 return None 

+

150 

+

151 # If the user quickly spawns multiple httpie processes 

+

152 # we don't want to end in a race. 

+

153 with open_with_lockfile(file) as stream: 

+

154 version_info = json.load(stream) 

+

155 

+

156 # We don't want to spam the user with too many warnings, 

+

157 # so we'll only warn every once a while (WARN_INTERNAL). 

+

158 current_date = datetime.now() 

+

159 last_warned_date = version_info['last_warned_date'] 

+

160 if last_warned_date is not None: 160 ↛ 161line 160 didn't jump to line 161 because the condition on line 160 was never true

+

161 earliest_warn_date = ( 

+

162 datetime.fromisoformat(last_warned_date) + WARN_INTERVAL 

+

163 ) 

+

164 if current_date < earliest_warn_date: 

+

165 return None 

+

166 

+

167 env.log_error(update_status, level=LogLevel.INFO) 

+

168 version_info['last_warned_date'] = current_date.isoformat() 

+

169 

+

170 with open_with_lockfile(file, 'w') as stream: 

+

171 json.dump(version_info, stream) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4___init___py.html b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4___init___py.html new file mode 100644 index 0000000000..4d410a4e67 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4___init___py.html @@ -0,0 +1,110 @@ + + + + + Coverage for httpie\plugins\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\plugins\__init__.py: + 100% +

+ +

+ 2 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2WARNING: The plugin API is still work in progress and will 

+

3 probably be completely reworked in the future. 

+

4 

+

5""" 

+

6from .base import ( 

+

7 AuthPlugin, FormatterPlugin, 

+

8 ConverterPlugin, TransportPlugin 

+

9) 

+

10 

+

11__all__ = ('AuthPlugin', 'ConverterPlugin', 'FormatterPlugin', 'TransportPlugin') 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_base_py.html b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_base_py.html new file mode 100644 index 0000000000..a74f4b4567 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_base_py.html @@ -0,0 +1,264 @@ + + + + + Coverage for httpie\plugins\base.py: 89% + + + + + +
+
+

+ Coverage for httpie\plugins\base.py: + 89% +

+ +

+ 38 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Tuple 

+

2 

+

3 

+

4class BasePlugin: 

+

5 # The name of the plugin, eg. "My auth". 

+

6 name = None 

+

7 

+

8 # Optional short description. It will be shown in the help 

+

9 # under --auth-type. 

+

10 description = None 

+

11 

+

12 # This be set automatically once the plugin has been loaded. 

+

13 package_name = None 

+

14 

+

15 

+

16class AuthPlugin(BasePlugin): 

+

17 """ 

+

18 Base auth plugin class. 

+

19 

+

20 See httpie-ntlm for an example auth plugin: 

+

21 

+

22 <https://github.com/httpie/httpie-ntlm> 

+

23 

+

24 See also `test_auth_plugins.py` 

+

25 

+

26 """ 

+

27 # The value that should be passed to --auth-type 

+

28 # to use this auth plugin. Eg. "my-auth" 

+

29 auth_type = None 

+

30 

+

31 # Set to `False` to make it possible to invoke this auth 

+

32 # plugin without requiring the user to specify credentials 

+

33 # through `--auth, -a`. 

+

34 auth_require = True 

+

35 

+

36 # By default the `-a` argument is parsed for `username:password`. 

+

37 # Set this to `False` to disable the parsing and error handling. 

+

38 auth_parse = True 

+

39 

+

40 # Set to `True` to make it possible for this auth 

+

41 # plugin to acquire credentials from the user’s netrc file(s). 

+

42 # It is used as a fallback when the credentials are not provided explicitly 

+

43 # through `--auth, -a`. Enabling this will allow skipping `--auth, -a` 

+

44 # even when `auth_require` is set `True` (provided that netrc provides 

+

45 # credential for a given host). 

+

46 netrc_parse = False 

+

47 

+

48 # If both `auth_parse` and `prompt_password` are set to `True`, 

+

49 # and the value of `-a` lacks the password part, 

+

50 # then the user will be prompted to type the password in. 

+

51 prompt_password = True 

+

52 

+

53 # Will be set to the raw value of `-a` (if provided) before 

+

54 # `get_auth()` gets called. If the credentials came from a netrc file, 

+

55 # then this is `None`. 

+

56 raw_auth = None 

+

57 

+

58 def get_auth(self, username: str = None, password: str = None): 

+

59 """ 

+

60 If `auth_parse` is set to `True`, then `username` 

+

61 and `password` contain the parsed credentials. 

+

62 

+

63 Use `self.raw_auth` to access the raw value passed through 

+

64 `--auth, -a`. 

+

65 

+

66 Return a ``requests.auth.AuthBase`` subclass instance. 

+

67 

+

68 """ 

+

69 raise NotImplementedError() 

+

70 

+

71 

+

72class TransportPlugin(BasePlugin): 

+

73 """ 

+

74 Requests transport adapter docs: 

+

75 

+

76 <https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters> 

+

77 

+

78 See httpie-unixsocket for an example transport plugin: 

+

79 

+

80 <https://github.com/httpie/httpie-unixsocket> 

+

81 

+

82 """ 

+

83 

+

84 # The URL prefix the adapter should be mount to. 

+

85 prefix = None 

+

86 

+

87 def get_adapter(self): 

+

88 """ 

+

89 Return a ``requests.adapters.BaseAdapter`` subclass instance to be 

+

90 mounted to ``self.prefix``. 

+

91 

+

92 """ 

+

93 raise NotImplementedError() 

+

94 

+

95 

+

96class ConverterPlugin(BasePlugin): 

+

97 """ 

+

98 Possibly converts binary response data for prettified terminal display. 

+

99 

+

100 See httpie-msgpack for an example converter plugin: 

+

101 

+

102 <https://github.com/rasky/httpie-msgpack>. 

+

103 

+

104 """ 

+

105 

+

106 def __init__(self, mime: str): 

+

107 self.mime = mime 

+

108 

+

109 def convert(self, body: bytes) -> Tuple[str, str]: 

+

110 """ 

+

111 Convert a binary body to a textual representation for the terminal 

+

112 and return a tuple containing the new Content-Type and content, e.g.: 

+

113 

+

114 ('application/json', '{}') 

+

115 

+

116 """ 

+

117 raise NotImplementedError 

+

118 

+

119 @classmethod 

+

120 def supports(cls, mime: str) -> bool: 

+

121 raise NotImplementedError 

+

122 

+

123 

+

124class FormatterPlugin(BasePlugin): 

+

125 """ 

+

126 Possibly formats response body & headers for prettified terminal display. 

+

127 

+

128 """ 

+

129 group_name = 'format' 

+

130 

+

131 def __init__(self, **kwargs): 

+

132 """ 

+

133 :param env: an class:`Environment` instance 

+

134 :param kwargs: additional keyword argument that some 

+

135 formatters might require. 

+

136 

+

137 """ 

+

138 self.enabled = True 

+

139 self.kwargs = kwargs 

+

140 self.format_options = kwargs['format_options'] 

+

141 

+

142 def format_headers(self, headers: str) -> str: 

+

143 """Return processed `headers` 

+

144 

+

145 :param headers: The headers as text. 

+

146 

+

147 """ 

+

148 return headers 

+

149 

+

150 def format_body(self, content: str, mime: str) -> str: 

+

151 """Return processed `content`. 

+

152 

+

153 :param mime: E.g., 'application/atom+xml'. 

+

154 :param content: The body content as text 

+

155 

+

156 """ 

+

157 return content 

+

158 

+

159 def format_metadata(self, metadata: str) -> str: 

+

160 """Return processed `metadata`. 

+

161 

+

162 :param metadata: The metadata as text. 

+

163 

+

164 """ 

+

165 return metadata 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_builtin_py.html b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_builtin_py.html new file mode 100644 index 0000000000..9bfbce2cbb --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_builtin_py.html @@ -0,0 +1,178 @@ + + + + + Coverage for httpie\plugins\builtin.py: 100% + + + + + +
+
+

+ Coverage for httpie\plugins\builtin.py: + 100% +

+ +

+ 39 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from base64 import b64encode 

+

2 

+

3import requests.auth 

+

4 

+

5from .base import AuthPlugin 

+

6 

+

7 

+

8# noinspection PyAbstractClass 

+

9class BuiltinAuthPlugin(AuthPlugin): 

+

10 package_name = '(builtin)' 

+

11 

+

12 

+

13class HTTPBasicAuth(requests.auth.HTTPBasicAuth): 

+

14 

+

15 def __call__( 

+

16 self, 

+

17 request: requests.PreparedRequest 

+

18 ) -> requests.PreparedRequest: 

+

19 """ 

+

20 Override username/password serialization to allow unicode. 

+

21 

+

22 See https://github.com/httpie/cli/issues/212 

+

23 

+

24 """ 

+

25 # noinspection PyTypeChecker 

+

26 request.headers['Authorization'] = type(self).make_header( 

+

27 self.username, self.password).encode('latin1') 

+

28 return request 

+

29 

+

30 @staticmethod 

+

31 def make_header(username: str, password: str) -> str: 

+

32 credentials = f'{username}:{password}' 

+

33 token = b64encode(credentials.encode()).strip().decode('latin1') 

+

34 return f'Basic {token}' 

+

35 

+

36 

+

37class HTTPBearerAuth(requests.auth.AuthBase): 

+

38 

+

39 def __init__(self, token: str) -> None: 

+

40 self.token = token 

+

41 

+

42 def __call__(self, request: requests.PreparedRequest) -> requests.PreparedRequest: 

+

43 request.headers['Authorization'] = f'Bearer {self.token}' 

+

44 return request 

+

45 

+

46 

+

47class BasicAuthPlugin(BuiltinAuthPlugin): 

+

48 name = 'Basic HTTP auth' 

+

49 auth_type = 'basic' 

+

50 netrc_parse = True 

+

51 

+

52 # noinspection PyMethodOverriding 

+

53 def get_auth(self, username: str, password: str) -> HTTPBasicAuth: 

+

54 return HTTPBasicAuth(username, password) 

+

55 

+

56 

+

57class DigestAuthPlugin(BuiltinAuthPlugin): 

+

58 name = 'Digest HTTP auth' 

+

59 auth_type = 'digest' 

+

60 netrc_parse = True 

+

61 

+

62 # noinspection PyMethodOverriding 

+

63 def get_auth( 

+

64 self, 

+

65 username: str, 

+

66 password: str 

+

67 ) -> requests.auth.HTTPDigestAuth: 

+

68 return requests.auth.HTTPDigestAuth(username, password) 

+

69 

+

70 

+

71class BearerAuthPlugin(BuiltinAuthPlugin): 

+

72 name = 'Bearer HTTP Auth' 

+

73 auth_type = 'bearer' 

+

74 netrc_parse = False 

+

75 auth_parse = False 

+

76 

+

77 # noinspection PyMethodOverriding 

+

78 def get_auth(self, **kwargs) -> requests.auth.HTTPDigestAuth: 

+

79 return HTTPBearerAuth(self.raw_auth) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_manager_py.html b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_manager_py.html new file mode 100644 index 0000000000..6bc907ee08 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_manager_py.html @@ -0,0 +1,220 @@ + + + + + Coverage for httpie\plugins\manager.py: 97% + + + + + +
+
+

+ Coverage for httpie\plugins\manager.py: + 97% +

+ +

+ 67 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import sys 

+

2import os 

+

3import warnings 

+

4 

+

5from itertools import groupby 

+

6from operator import attrgetter 

+

7from typing import Dict, List, Type, Iterator, Iterable, Optional, ContextManager 

+

8from pathlib import Path 

+

9from contextlib import contextmanager, nullcontext 

+

10 

+

11from ..compat import importlib_metadata, find_entry_points, get_dist_name 

+

12 

+

13from ..utils import repr_dict, get_site_paths 

+

14from . import AuthPlugin, ConverterPlugin, FormatterPlugin, TransportPlugin 

+

15from .base import BasePlugin 

+

16 

+

17 

+

18ENTRY_POINT_CLASSES = { 

+

19 'httpie.plugins.auth.v1': AuthPlugin, 

+

20 'httpie.plugins.converter.v1': ConverterPlugin, 

+

21 'httpie.plugins.formatter.v1': FormatterPlugin, 

+

22 'httpie.plugins.transport.v1': TransportPlugin 

+

23} 

+

24ENTRY_POINT_NAMES = list(ENTRY_POINT_CLASSES.keys()) 

+

25 

+

26 

+

27@contextmanager 

+

28def _load_directories(site_dirs: Iterable[Path]) -> Iterator[None]: 

+

29 plugin_dirs = [ 

+

30 os.fspath(site_dir) 

+

31 for site_dir in site_dirs 

+

32 ] 

+

33 sys.path.extend(plugin_dirs) 

+

34 try: 

+

35 yield 

+

36 finally: 

+

37 for plugin_dir in plugin_dirs: 

+

38 sys.path.remove(plugin_dir) 

+

39 

+

40 

+

41def enable_plugins(plugins_dir: Optional[Path]) -> ContextManager[None]: 

+

42 if plugins_dir is None: 42 ↛ 43line 42 didn't jump to line 43 because the condition on line 42 was never true

+

43 return nullcontext() 

+

44 else: 

+

45 return _load_directories(get_site_paths(plugins_dir)) 

+

46 

+

47 

+

48class PluginManager(list): 

+

49 def register(self, *plugins: Type[BasePlugin]): 

+

50 for plugin in plugins: 

+

51 self.append(plugin) 

+

52 

+

53 def unregister(self, plugin: Type[BasePlugin]): 

+

54 self.remove(plugin) 

+

55 

+

56 def filter(self, by_type=Type[BasePlugin]): 

+

57 return [plugin for plugin in self if issubclass(plugin, by_type)] 

+

58 

+

59 def iter_entry_points(self, directory: Optional[Path] = None): 

+

60 with enable_plugins(directory): 

+

61 eps = importlib_metadata.entry_points() 

+

62 

+

63 for entry_point_name in ENTRY_POINT_NAMES: 

+

64 yield from find_entry_points(eps, group=entry_point_name) 

+

65 

+

66 def load_installed_plugins(self, directory: Optional[Path] = None): 

+

67 for entry_point in self.iter_entry_points(directory): 

+

68 plugin_name = get_dist_name(entry_point) 

+

69 try: 

+

70 plugin = entry_point.load() 

+

71 except BaseException as exc: 

+

72 warnings.warn( 

+

73 f'While loading "{plugin_name}", an error occurred: {exc}\n' 

+

74 f'For uninstallations, please use either "httpie plugins uninstall {plugin_name}" ' 

+

75 f'or "pip uninstall {plugin_name}" (depending on how you installed it in the first ' 

+

76 'place).' 

+

77 ) 

+

78 continue 

+

79 plugin.package_name = plugin_name 

+

80 self.register(plugin) 

+

81 

+

82 # Auth 

+

83 def get_auth_plugins(self) -> List[Type[AuthPlugin]]: 

+

84 return self.filter(AuthPlugin) 

+

85 

+

86 def get_auth_plugin_mapping(self) -> Dict[str, Type[AuthPlugin]]: 

+

87 return { 

+

88 plugin.auth_type: plugin for plugin in self.get_auth_plugins() 

+

89 } 

+

90 

+

91 def get_auth_plugin(self, auth_type: str) -> Type[AuthPlugin]: 

+

92 return self.get_auth_plugin_mapping()[auth_type] 

+

93 

+

94 # Output processing 

+

95 def get_formatters(self) -> List[Type[FormatterPlugin]]: 

+

96 return self.filter(FormatterPlugin) 

+

97 

+

98 def get_formatters_grouped(self) -> Dict[str, List[Type[FormatterPlugin]]]: 

+

99 return { 

+

100 group_name: list(group) 

+

101 for group_name, group 

+

102 in groupby(self.get_formatters(), key=attrgetter('group_name')) 

+

103 } 

+

104 

+

105 def get_converters(self) -> List[Type[ConverterPlugin]]: 

+

106 return self.filter(ConverterPlugin) 

+

107 

+

108 # Adapters 

+

109 def get_transport_plugins(self) -> List[Type[TransportPlugin]]: 

+

110 return self.filter(TransportPlugin) 

+

111 

+

112 def __str__(self): 

+

113 return repr_dict({ 

+

114 'adapters': self.get_transport_plugins(), 

+

115 'auth': self.get_auth_plugins(), 

+

116 'converters': self.get_converters(), 

+

117 'formatters': self.get_formatters(), 

+

118 }) 

+

119 

+

120 def __repr__(self): 

+

121 return f'<{type(self).__name__} {self}>' 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_registry_py.html b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_registry_py.html new file mode 100644 index 0000000000..714e0ba627 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_cd3f0923f62395a4_registry_py.html @@ -0,0 +1,120 @@ + + + + + Coverage for httpie\plugins\registry.py: 100% + + + + + +
+
+

+ Coverage for httpie\plugins\registry.py: + 100% +

+ +

+ 8 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from .manager import PluginManager 

+

2from .builtin import BasicAuthPlugin, DigestAuthPlugin, BearerAuthPlugin 

+

3from ..output.formatters.headers import HeadersFormatter 

+

4from ..output.formatters.json import JSONFormatter 

+

5from ..output.formatters.xml import XMLFormatter 

+

6from ..output.formatters.colors import ColorFormatter 

+

7 

+

8 

+

9plugin_manager = PluginManager() 

+

10 

+

11 

+

12# Register all built-in plugins. 

+

13plugin_manager.register( 

+

14 BasicAuthPlugin, 

+

15 DigestAuthPlugin, 

+

16 BearerAuthPlugin, 

+

17 HeadersFormatter, 

+

18 JSONFormatter, 

+

19 XMLFormatter, 

+

20 ColorFormatter, 

+

21) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e___init___py.html b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e___init___py.html new file mode 100644 index 0000000000..14bc927f70 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e___init___py.html @@ -0,0 +1,99 @@ + + + + + Coverage for httpie\legacy\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\legacy\__init__.py: + 100% +

+ +

+ 0 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_1_0_session_cookie_format_py.html b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_1_0_session_cookie_format_py.html new file mode 100644 index 0000000000..8973213909 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_1_0_session_cookie_format_py.html @@ -0,0 +1,199 @@ + + + + + Coverage for httpie\legacy\v3_1_0_session_cookie_format.py: 98% + + + + + +
+
+

+ Coverage for httpie\legacy\v3_1_0_session_cookie_format.py: + 98% +

+ +

+ 31 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1import argparse 

+

2from typing import Any, Type, List, Dict, TYPE_CHECKING 

+

3 

+

4if TYPE_CHECKING: 

+

5 from httpie.sessions import Session 

+

6 

+

7 

+

8INSECURE_COOKIE_JAR_WARNING = '''\ 

+

9Outdated layout detected for the current session. Please consider updating it, 

+

10in order to not get affected by potential security problems. 

+

11 

+

12For fixing the current session: 

+

13 

+

14 With binding all cookies to the current host (secure): 

+

15 $ httpie cli sessions upgrade --bind-cookies {hostname} {session_id} 

+

16 

+

17 Without binding cookies (leaving them as is) (insecure): 

+

18 $ httpie cli sessions upgrade {hostname} {session_id} 

+

19''' 

+

20 

+

21 

+

22INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS = '''\ 

+

23 

+

24For fixing all named sessions: 

+

25 

+

26 With binding all cookies to the current host (secure): 

+

27 $ httpie cli sessions upgrade-all --bind-cookies 

+

28 

+

29 Without binding cookies (leaving them as is) (insecure): 

+

30 $ httpie cli sessions upgrade-all 

+

31''' 

+

32 

+

33INSECURE_COOKIE_SECURITY_LINK = '\nSee https://pie.co/docs/security for more information.' 

+

34 

+

35 

+

36def pre_process(session: 'Session', cookies: Any) -> List[Dict[str, Any]]: 

+

37 """Load the given cookies to the cookie jar while maintaining 

+

38 support for the old cookie layout.""" 

+

39 

+

40 is_old_style = isinstance(cookies, dict) 

+

41 if is_old_style: 

+

42 normalized_cookies = [ 

+

43 { 

+

44 'name': key, 

+

45 **value 

+

46 } 

+

47 for key, value in cookies.items() 

+

48 ] 

+

49 else: 

+

50 normalized_cookies = cookies 

+

51 

+

52 should_issue_warning = is_old_style and any( 

+

53 cookie.get('domain', '') == '' 

+

54 for cookie in normalized_cookies 

+

55 ) 

+

56 

+

57 if should_issue_warning: 

+

58 warning = INSECURE_COOKIE_JAR_WARNING.format(hostname=session.bound_host, session_id=session.session_id) 

+

59 if not session.is_anonymous: 

+

60 warning += INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS 

+

61 warning += INSECURE_COOKIE_SECURITY_LINK 

+

62 session.warn_legacy_usage(warning) 

+

63 

+

64 return normalized_cookies 

+

65 

+

66 

+

67def post_process( 

+

68 normalized_cookies: List[Dict[str, Any]], 

+

69 *, 

+

70 original_type: Type[Any] 

+

71) -> Any: 

+

72 """Convert the cookies to their original format for 

+

73 maximum compatibility.""" 

+

74 

+

75 if issubclass(original_type, dict): 

+

76 return { 

+

77 cookie.pop('name'): cookie 

+

78 for cookie in normalized_cookies 

+

79 } 

+

80 else: 

+

81 return normalized_cookies 

+

82 

+

83 

+

84def fix_layout(session: 'Session', hostname: str, args: argparse.Namespace) -> None: 

+

85 if not isinstance(session['cookies'], dict): 

+

86 return None 

+

87 

+

88 session['cookies'] = [ 

+

89 { 

+

90 'name': key, 

+

91 **value 

+

92 } 

+

93 for key, value in session['cookies'].items() 

+

94 ] 

+

95 for cookie in session.cookies: 

+

96 if cookie.domain == '': 96 ↛ 95line 96 didn't jump to line 95 because the condition on line 96 was always true

+

97 if args.bind_cookies: 

+

98 cookie.domain = hostname 

+

99 else: 

+

100 cookie._rest['is_explicit_none'] = True 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_2_0_session_header_format_py.html b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_2_0_session_header_format_py.html new file mode 100644 index 0000000000..32e1f9f479 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_d97044aac8d9c56e_v3_2_0_session_header_format_py.html @@ -0,0 +1,172 @@ + + + + + Coverage for httpie\legacy\v3_2_0_session_header_format.py: 100% + + + + + +
+
+

+ Coverage for httpie\legacy\v3_2_0_session_header_format.py: + 100% +

+ +

+ 25 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Any, Type, List, Dict, TYPE_CHECKING 

+

2 

+

3if TYPE_CHECKING: 

+

4 from httpie.sessions import Session 

+

5 

+

6 

+

7OLD_HEADER_STORE_WARNING = '''\ 

+

8Outdated layout detected for the current session. Please consider updating it, 

+

9in order to use the latest features regarding the header layout. 

+

10 

+

11For fixing the current session: 

+

12 

+

13 $ httpie cli sessions upgrade {hostname} {session_id} 

+

14''' 

+

15 

+

16OLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS = '''\ 

+

17 

+

18For fixing all named sessions: 

+

19 

+

20 $ httpie cli sessions upgrade-all 

+

21''' 

+

22 

+

23OLD_HEADER_STORE_LINK = '\nSee $INSERT_LINK for more information.' 

+

24 

+

25 

+

26def pre_process(session: 'Session', headers: Any) -> List[Dict[str, Any]]: 

+

27 """Serialize the headers into a unified form and issue a warning if 

+

28 the session file is using the old layout.""" 

+

29 

+

30 is_old_style = isinstance(headers, dict) 

+

31 if is_old_style: 

+

32 normalized_headers = list(headers.items()) 

+

33 else: 

+

34 normalized_headers = [ 

+

35 (item['name'], item['value']) 

+

36 for item in headers 

+

37 ] 

+

38 

+

39 if is_old_style: 

+

40 warning = OLD_HEADER_STORE_WARNING.format(hostname=session.bound_host, session_id=session.session_id) 

+

41 if not session.is_anonymous: 

+

42 warning += OLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS 

+

43 warning += OLD_HEADER_STORE_LINK 

+

44 session.warn_legacy_usage(warning) 

+

45 

+

46 return normalized_headers 

+

47 

+

48 

+

49def post_process( 

+

50 normalized_headers: List[Dict[str, Any]], 

+

51 *, 

+

52 original_type: Type[Any] 

+

53) -> Any: 

+

54 """Deserialize given header store into the original form it was 

+

55 used in.""" 

+

56 

+

57 if issubclass(original_type, dict): 

+

58 # For the legacy behavior, preserve the last value. 

+

59 return { 

+

60 item['name']: item['value'] 

+

61 for item in normalized_headers 

+

62 } 

+

63 else: 

+

64 return normalized_headers 

+

65 

+

66 

+

67def fix_layout(session: 'Session', *args, **kwargs) -> None: 

+

68 from httpie.sessions import materialize_headers 

+

69 

+

70 if not isinstance(session['headers'], dict): 

+

71 return None 

+

72 

+

73 session['headers'] = materialize_headers(session['headers']) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4___init___py.html b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4___init___py.html new file mode 100644 index 0000000000..e8a9640595 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4___init___py.html @@ -0,0 +1,119 @@ + + + + + Coverage for httpie\cli\nested_json\__init__.py: 100% + + + + + +
+
+

+ Coverage for httpie\cli\nested_json\__init__.py: + 100% +

+ +

+ 4 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1""" 

+

2A library for parsing the HTTPie nested JSON key syntax and constructing the resulting objects. 

+

3 

+

4<https://httpie.io/docs/cli/nested-json> 

+

5 

+

6It has no dependencies. 

+

7 

+

8""" 

+

9from .interpret import interpret_nested_json, unwrap_top_level_list_if_needed 

+

10from .errors import NestedJSONSyntaxError 

+

11from .tokens import EMPTY_STRING, NestedJSONArray 

+

12 

+

13 

+

14__all__ = [ 

+

15 'interpret_nested_json', 

+

16 'unwrap_top_level_list_if_needed', 

+

17 'EMPTY_STRING', 

+

18 'NestedJSONArray', 

+

19 'NestedJSONSyntaxError' 

+

20] 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_errors_py.html b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_errors_py.html new file mode 100644 index 0000000000..bca3346b40 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_errors_py.html @@ -0,0 +1,126 @@ + + + + + Coverage for httpie\cli\nested_json\errors.py: 100% + + + + + +
+
+

+ Coverage for httpie\cli\nested_json\errors.py: + 100% +

+ +

+ 14 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Optional 

+

2 

+

3from .tokens import Token, HIGHLIGHTER 

+

4 

+

5 

+

6class NestedJSONSyntaxError(ValueError): 

+

7 def __init__( 

+

8 self, 

+

9 source: str, 

+

10 token: Optional[Token], 

+

11 message: str, 

+

12 message_kind: str = 'Syntax', 

+

13 ) -> None: 

+

14 self.source = source 

+

15 self.token = token 

+

16 self.message = message 

+

17 self.message_kind = message_kind 

+

18 

+

19 def __str__(self): 

+

20 lines = [f'HTTPie {self.message_kind} Error: {self.message}'] 

+

21 if self.token is not None: 

+

22 lines.append(self.source) 

+

23 lines.append( 

+

24 ' ' * self.token.start 

+

25 + HIGHLIGHTER * (self.token.end - self.token.start) 

+

26 ) 

+

27 return '\n'.join(lines) 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_interpret_py.html b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_interpret_py.html new file mode 100644 index 0000000000..eb6981f870 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_interpret_py.html @@ -0,0 +1,228 @@ + + + + + Coverage for httpie\cli\nested_json\interpret.py: 94% + + + + + +
+
+

+ Coverage for httpie\cli\nested_json\interpret.py: + 94% +

+ +

+ 77 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Type, Union, Any, Iterable, Tuple 

+

2 

+

3from .parse import parse, assert_cant_happen 

+

4from .errors import NestedJSONSyntaxError 

+

5from .tokens import EMPTY_STRING, TokenKind, Token, PathAction, Path, NestedJSONArray 

+

6 

+

7 

+

8__all__ = [ 

+

9 'interpret_nested_json', 

+

10 'unwrap_top_level_list_if_needed', 

+

11] 

+

12 

+

13JSONType = Type[Union[dict, list, int, float, str]] 

+

14JSON_TYPE_MAPPING = { 

+

15 dict: 'object', 

+

16 list: 'array', 

+

17 int: 'number', 

+

18 float: 'number', 

+

19 str: 'string', 

+

20} 

+

21 

+

22 

+

23def interpret_nested_json(pairs: Iterable[Tuple[str, str]]) -> dict: 

+

24 context = None 

+

25 for key, value in pairs: 

+

26 context = interpret(context, key, value) 

+

27 return wrap_with_dict(context) 

+

28 

+

29 

+

30def interpret(context: Any, key: str, value: Any) -> Any: 

+

31 cursor = context 

+

32 paths = list(parse(key)) 

+

33 paths.append(Path(PathAction.SET, value)) 

+

34 

+

35 # noinspection PyShadowingNames 

+

36 def type_check(index: int, path: Path, expected_type: JSONType): 

+

37 if not isinstance(cursor, expected_type): 

+

38 if path.tokens: 

+

39 pseudo_token = Token( 

+

40 kind=TokenKind.PSEUDO, 

+

41 value='', 

+

42 start=path.tokens[0].start, 

+

43 end=path.tokens[-1].end, 

+

44 ) 

+

45 else: 

+

46 pseudo_token = None 

+

47 cursor_type = JSON_TYPE_MAPPING.get(type(cursor), type(cursor).__name__) 

+

48 required_type = JSON_TYPE_MAPPING[expected_type] 

+

49 message = f'Cannot perform {path.kind.to_string()!r} based access on ' 

+

50 message += repr(''.join(path.reconstruct() for path in paths[:index])) 

+

51 message += f' which has a type of {cursor_type!r} but this operation' 

+

52 message += f' requires a type of {required_type!r}.' 

+

53 raise NestedJSONSyntaxError( 

+

54 source=key, 

+

55 token=pseudo_token, 

+

56 message=message, 

+

57 message_kind='Type', 

+

58 ) 

+

59 

+

60 def object_for(kind: PathAction) -> Any: 

+

61 if kind is PathAction.KEY: 

+

62 return {} 

+

63 elif kind in {PathAction.INDEX, PathAction.APPEND}: 63 ↛ 66line 63 didn't jump to line 66 because the condition on line 63 was always true

+

64 return [] 

+

65 else: 

+

66 assert_cant_happen() 

+

67 

+

68 for index, (path, next_path) in enumerate(zip(paths, paths[1:])): 68 ↛ 104line 68 didn't jump to line 104 because the loop on line 68 didn't complete

+

69 # If there is no context yet, set it. 

+

70 if cursor is None: 

+

71 context = cursor = object_for(path.kind) 

+

72 if path.kind is PathAction.KEY: 

+

73 type_check(index, path, dict) 

+

74 if next_path.kind is PathAction.SET: 

+

75 cursor[path.accessor] = next_path.accessor 

+

76 break 

+

77 cursor = cursor.setdefault(path.accessor, object_for(next_path.kind)) 

+

78 elif path.kind is PathAction.INDEX: 

+

79 type_check(index, path, list) 

+

80 if path.accessor < 0: 

+

81 raise NestedJSONSyntaxError( 

+

82 source=key, 

+

83 token=path.tokens[1], 

+

84 message='Negative indexes are not supported.', 

+

85 message_kind='Value', 

+

86 ) 

+

87 cursor.extend([None] * (path.accessor - len(cursor) + 1)) 

+

88 if next_path.kind is PathAction.SET: 

+

89 cursor[path.accessor] = next_path.accessor 

+

90 break 

+

91 if cursor[path.accessor] is None: 

+

92 cursor[path.accessor] = object_for(next_path.kind) 

+

93 cursor = cursor[path.accessor] 

+

94 elif path.kind is PathAction.APPEND: 94 ↛ 102line 94 didn't jump to line 102 because the condition on line 94 was always true

+

95 type_check(index, path, list) 

+

96 if next_path.kind is PathAction.SET: 

+

97 cursor.append(next_path.accessor) 

+

98 break 

+

99 cursor.append(object_for(next_path.kind)) 

+

100 cursor = cursor[-1] 

+

101 else: 

+

102 assert_cant_happen() 

+

103 

+

104 return context 

+

105 

+

106 

+

107def wrap_with_dict(context): 

+

108 if context is None: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true

+

109 return {} 

+

110 elif isinstance(context, list): 

+

111 return { 

+

112 EMPTY_STRING: NestedJSONArray(context), 

+

113 } 

+

114 else: 

+

115 assert isinstance(context, dict) 

+

116 return context 

+

117 

+

118 

+

119def unwrap_top_level_list_if_needed(data: dict): 

+

120 """ 

+

121 Propagate the top-level list, if that’s what we got. 

+

122 

+

123 """ 

+

124 if len(data) == 1: 

+

125 key, value = list(data.items())[0] 

+

126 if isinstance(value, NestedJSONArray): 

+

127 assert key == EMPTY_STRING 

+

128 return value 

+

129 return data 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_parse_py.html b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_parse_py.html new file mode 100644 index 0000000000..932ddb2dc5 --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_parse_py.html @@ -0,0 +1,292 @@ + + + + + Coverage for httpie\cli\nested_json\parse.py: 94% + + + + + +
+
+

+ Coverage for httpie\cli\nested_json\parse.py: + 94% +

+ +

+ 107 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from typing import Iterator 

+

2 

+

3from .errors import NestedJSONSyntaxError 

+

4from .tokens import ( 

+

5 EMPTY_STRING, 

+

6 BACKSLASH, 

+

7 TokenKind, 

+

8 OPERATORS, 

+

9 SPECIAL_CHARS, 

+

10 LITERAL_TOKENS, 

+

11 Token, 

+

12 PathAction, 

+

13 Path, 

+

14) 

+

15 

+

16 

+

17__all__ = [ 

+

18 'parse', 

+

19 'assert_cant_happen', 

+

20] 

+

21 

+

22 

+

23def parse(source: str) -> Iterator[Path]: 

+

24 """ 

+

25 start: root_path path* 

+

26 root_path: (literal | index_path | append_path) 

+

27 literal: TEXT | NUMBER 

+

28 

+

29 path: 

+

30 key_path 

+

31 | index_path 

+

32 | append_path 

+

33 key_path: LEFT_BRACKET TEXT RIGHT_BRACKET 

+

34 index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET 

+

35 append_path: LEFT_BRACKET RIGHT_BRACKET 

+

36 

+

37 """ 

+

38 

+

39 tokens = list(tokenize(source)) 

+

40 cursor = 0 

+

41 

+

42 def can_advance(): 

+

43 return cursor < len(tokens) 

+

44 

+

45 # noinspection PyShadowingNames 

+

46 def expect(*kinds): 

+

47 nonlocal cursor 

+

48 assert kinds 

+

49 if can_advance(): 

+

50 token = tokens[cursor] 

+

51 cursor += 1 

+

52 if token.kind in kinds: 

+

53 return token 

+

54 elif tokens: 54 ↛ 60line 54 didn't jump to line 60 because the condition on line 54 was always true

+

55 token = tokens[-1]._replace( 

+

56 start=tokens[-1].end + 0, 

+

57 end=tokens[-1].end + 1, 

+

58 ) 

+

59 else: 

+

60 token = None 

+

61 if len(kinds) == 1: 

+

62 suffix = kinds[0].to_name() 

+

63 else: 

+

64 suffix = ', '.join(kind.to_name() for kind in kinds[:-1]) 

+

65 suffix += ' or ' + kinds[-1].to_name() 

+

66 message = f'Expecting {suffix}' 

+

67 raise NestedJSONSyntaxError(source, token, message) 

+

68 

+

69 # noinspection PyShadowingNames 

+

70 def parse_root(): 

+

71 tokens = [] 

+

72 if not can_advance(): 

+

73 return Path( 

+

74 kind=PathAction.KEY, 

+

75 accessor=EMPTY_STRING, 

+

76 is_root=True 

+

77 ) 

+

78 # (literal | index_path | append_path)? 

+

79 token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET) 

+

80 tokens.append(token) 

+

81 if token.kind in LITERAL_TOKENS: 

+

82 action = PathAction.KEY 

+

83 value = str(token.value) 

+

84 elif token.kind is TokenKind.LEFT_BRACKET: 84 ↛ 97line 84 didn't jump to line 97 because the condition on line 84 was always true

+

85 token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET) 

+

86 tokens.append(token) 

+

87 if token.kind is TokenKind.NUMBER: 

+

88 action = PathAction.INDEX 

+

89 value = token.value 

+

90 tokens.append(expect(TokenKind.RIGHT_BRACKET)) 

+

91 elif token.kind is TokenKind.RIGHT_BRACKET: 91 ↛ 95line 91 didn't jump to line 95 because the condition on line 91 was always true

+

92 action = PathAction.APPEND 

+

93 value = None 

+

94 else: 

+

95 assert_cant_happen() 

+

96 else: 

+

97 assert_cant_happen() 

+

98 # noinspection PyUnboundLocalVariable 

+

99 return Path( 

+

100 kind=action, 

+

101 accessor=value, 

+

102 tokens=tokens, 

+

103 is_root=True 

+

104 ) 

+

105 

+

106 yield parse_root() 

+

107 

+

108 # path* 

+

109 while can_advance(): 

+

110 path_tokens = [expect(TokenKind.LEFT_BRACKET)] 

+

111 token = expect(TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET) 

+

112 path_tokens.append(token) 

+

113 if token.kind is TokenKind.RIGHT_BRACKET: 

+

114 path = Path(PathAction.APPEND, tokens=path_tokens) 

+

115 elif token.kind is TokenKind.TEXT: 

+

116 path = Path(PathAction.KEY, token.value, tokens=path_tokens) 

+

117 path_tokens.append(expect(TokenKind.RIGHT_BRACKET)) 

+

118 elif token.kind is TokenKind.NUMBER: 118 ↛ 122line 118 didn't jump to line 122 because the condition on line 118 was always true

+

119 path = Path(PathAction.INDEX, token.value, tokens=path_tokens) 

+

120 path_tokens.append(expect(TokenKind.RIGHT_BRACKET)) 

+

121 else: 

+

122 assert_cant_happen() 

+

123 # noinspection PyUnboundLocalVariable 

+

124 yield path 

+

125 

+

126 

+

127def tokenize(source: str) -> Iterator[Token]: 

+

128 cursor = 0 

+

129 backslashes = 0 

+

130 buffer = [] 

+

131 

+

132 def send_buffer() -> Iterator[Token]: 

+

133 nonlocal backslashes 

+

134 if not buffer: 

+

135 return None 

+

136 

+

137 value = ''.join(buffer) 

+

138 kind = TokenKind.TEXT 

+

139 if not backslashes: 

+

140 for variation, kind in [ 

+

141 (int, TokenKind.NUMBER), 

+

142 (check_escaped_int, TokenKind.TEXT), 

+

143 ]: 

+

144 try: 

+

145 value = variation(value) 

+

146 except ValueError: 

+

147 continue 

+

148 else: 

+

149 break 

+

150 yield Token( 

+

151 kind=kind, 

+

152 value=value, 

+

153 start=cursor - (len(buffer) + backslashes), 

+

154 end=cursor, 

+

155 ) 

+

156 buffer.clear() 

+

157 backslashes = 0 

+

158 

+

159 def can_advance() -> bool: 

+

160 return cursor < len(source) 

+

161 

+

162 while can_advance(): 

+

163 index = source[cursor] 

+

164 if index in OPERATORS: 

+

165 yield from send_buffer() 

+

166 yield Token(OPERATORS[index], index, cursor, cursor + 1) 

+

167 elif index == BACKSLASH and can_advance(): 

+

168 if source[cursor + 1] in SPECIAL_CHARS: 

+

169 backslashes += 1 

+

170 else: 

+

171 buffer.append(index) 

+

172 buffer.append(source[cursor + 1]) 

+

173 cursor += 1 

+

174 else: 

+

175 buffer.append(index) 

+

176 cursor += 1 

+

177 

+

178 yield from send_buffer() 

+

179 

+

180 

+

181def check_escaped_int(value: str) -> str: 

+

182 if not value.startswith(BACKSLASH): 

+

183 raise ValueError('Not an escaped int') 

+

184 try: 

+

185 int(value[1:]) 

+

186 except ValueError as exc: 

+

187 raise ValueError('Not an escaped int') from exc 

+

188 else: 

+

189 return value[1:] 

+

190 

+

191 

+

192def assert_cant_happen(): 

+

193 raise ValueError('Unexpected value') 

+
+ + + diff --git a/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_tokens_py.html b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_tokens_py.html new file mode 100644 index 0000000000..8e9a58d55e --- /dev/null +++ b/courseProjectDocs/Setup/testCoverage/z_ed165166c68eaac4_tokens_py.html @@ -0,0 +1,179 @@ + + + + + Coverage for httpie\cli\nested_json\tokens.py: 85% + + + + + +
+
+

+ Coverage for httpie\cli\nested_json\tokens.py: + 85% +

+ +

+ 49 statements   + + + + +

+

+ « prev     + ^ index     + » next +       + coverage.py v7.16.2, + created at 2026-09-28 11:55 -0400 +

+ +
+
+
+

1from enum import Enum, auto 

+

2from typing import NamedTuple, Union, Optional, List 

+

3 

+

4EMPTY_STRING = '' 

+

5HIGHLIGHTER = '^' 

+

6OPEN_BRACKET = '[' 

+

7CLOSE_BRACKET = ']' 

+

8BACKSLASH = '\\' 

+

9 

+

10 

+

11class TokenKind(Enum): 

+

12 TEXT = auto() 

+

13 NUMBER = auto() 

+

14 LEFT_BRACKET = auto() 

+

15 RIGHT_BRACKET = auto() 

+

16 PSEUDO = auto() # Not a real token, use when representing location only. 

+

17 

+

18 def to_name(self) -> str: 

+

19 for key, value in OPERATORS.items(): 

+

20 if value is self: 

+

21 return repr(key) 

+

22 else: 

+

23 return 'a ' + self.name.lower() 

+

24 

+

25 

+

26OPERATORS = { 

+

27 OPEN_BRACKET: TokenKind.LEFT_BRACKET, 

+

28 CLOSE_BRACKET: TokenKind.RIGHT_BRACKET, 

+

29} 

+

30SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH} 

+

31LITERAL_TOKENS = [ 

+

32 TokenKind.TEXT, 

+

33 TokenKind.NUMBER, 

+

34] 

+

35 

+

36 

+

37class Token(NamedTuple): 

+

38 kind: TokenKind 

+

39 value: Union[str, int] 

+

40 start: int 

+

41 end: int 

+

42 

+

43 

+

44class PathAction(Enum): 

+

45 KEY = auto() 

+

46 INDEX = auto() 

+

47 APPEND = auto() 

+

48 # Pseudo action, used by the interpreter 

+

49 SET = auto() 

+

50 

+

51 def to_string(self) -> str: 

+

52 return self.name.lower() 

+

53 

+

54 

+

55class Path: 

+

56 def __init__( 

+

57 self, 

+

58 kind: PathAction, 

+

59 accessor: Optional[Union[str, int]] = None, 

+

60 tokens: Optional[List[Token]] = None, 

+

61 is_root: bool = False, 

+

62 ): 

+

63 self.kind = kind 

+

64 self.accessor = accessor 

+

65 self.tokens = tokens or [] 

+

66 self.is_root = is_root 

+

67 

+

68 def reconstruct(self) -> str: 

+

69 if self.kind is PathAction.KEY: 69 ↛ 73line 69 didn't jump to line 73 because the condition on line 69 was always true

+

70 if self.is_root: 

+

71 return str(self.accessor) 

+

72 return OPEN_BRACKET + self.accessor + CLOSE_BRACKET 

+

73 elif self.kind is PathAction.INDEX: 

+

74 return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET 

+

75 elif self.kind is PathAction.APPEND: 

+

76 return OPEN_BRACKET + CLOSE_BRACKET 

+

77 

+

78 

+

79class NestedJSONArray(list): 

+

80 """Denotes a top-level JSON array.""" 

+
+ + + diff --git a/courseProjectDocs/Setup/testResults/report.html b/courseProjectDocs/Setup/testResults/report.html new file mode 100644 index 0000000000..669645e580 --- /dev/null +++ b/courseProjectDocs/Setup/testResults/report.html @@ -0,0 +1,1094 @@ + + + + + report.html + + + + +

report.html

+

Report generated on 28-Sep-2026 at 11:59:34 by pytest-html + v4.2.0

+
+

Environment

+
+
+ + + + + +
+
+

Summary

+
+
+

1009 tests took 00:02:10.

+

(Un)check the boxes to filter the results.

+
+ +
+
+
+
+ + 2 Failed, + + 1003 Passed, + + 19 Skipped, + + 4 Expected failures, + + 0 Unexpected passes, + + 0 Errors, + + 0 Reruns + + 0 Retried, +
+
+  /  +
+
+
+
+
+
+
+
+ + + + + + + + + +
ResultTestDurationLinks
+ + + \ No newline at end of file diff --git a/courseProjectDocs/project-proposal.md b/courseProjectDocs/project-proposal.md new file mode 100644 index 0000000000..4bcb2c7a63 --- /dev/null +++ b/courseProjectDocs/project-proposal.md @@ -0,0 +1,119 @@ +# Project Proposal — HTTPie CLI + +**Course:** SWEN-777 Software Quality Assurance +**Team:** Leon, Tenzin +**Upstream project:** HTTPie CLI — https://github.com/httpie/cli +**Fork:** https://github.com/itsleeyawn/cli +**Baseline analyzed:** v3.2.4 + +## Project Overview + +HTTPie is a command-line HTTP client written in Python. It wraps the `requests` +library behind a syntax designed to be readable by humans rather than by shell +scripts, and adds colorized and formatted output, persistent sessions, +`wget`-style downloads, and a plugin system for auth and transport. The `http` +and `https` commands are the primary entry points. + +We picked it for three reasons. + +It is a real production tool with a real user base, not a toy repository, so the +quality problems we find are problems that affect people. It is also small +enough to reason about: our analysis covers 78 source files and 7,039 logical +lines of code, which a three-person team can actually read in a semester rather +than sample from. + +Second, it has a substantial existing test suite — 409 test functions across 37 +test modules — built on pytest with a local `pytest-httpbin` server. That gives +us something to analyze for oracle quality in the next deliverable instead of +starting from zero coverage. + +Third, its architecture creates a natural testability gradient. The CLI parsing, +HTTP client, and output formatting layers are separated, but the entry point +(`core.py`) coordinates all of them, so we expect a small number of heavily +coupled modules surrounded by many easily testable ones. That is a useful shape +for a quality study because it gives us both ends of the spectrum in one +codebase. + +## Key Quality Metrics + +We are measuring **maintainability** and **testability**. Collection code and +reproduction instructions are in `courseProjectCode/Metrics/`. + +### Maintainability + +Measured with the Maintainability Index (MI), the standard composite of Halstead +volume, cyclomatic complexity, and lines of code, computed with `radon`. MI is +reported on a 0–100 scale with conventional bands: above 65 is maintainable, +20–65 is moderate risk, below 20 is high risk. + +Baseline results: + +| Measure | Value | +| --- | --- | +| Files analyzed | 78 | +| Total SLOC | 7,039 | +| Mean MI | 74.04 | +| Median MI | 73.48 | +| Files below MI 65 (moderate risk) | 31 | +| Files below MI 20 (high risk) | 0 | +| Mean cyclomatic complexity per unit | 2.38 | +| Highest cyclomatic complexity in a single unit | 27 (`core.py`) | + +The project is healthy on average but not uniformly. Nothing is in the high-risk +band, yet 40% of files sit in the moderate band, and the distribution is skewed: +`cli/argparser.py` (MI 24.67) and `cli/options.py` (MI 38.28) are far worse than +the mean. Argument parsing is doing a large amount of work in one place. + +### Testability + +There is no single accepted testability metric, so we report the structural +properties that drive test effort and combine them into a defined heuristic +score: average cyclomatic complexity (paths to cover), coupling via resolved +internal and external imports (collaborators to stub or construct), average +parameter count (setup cost per case), and public definition count (entry points +needing their own tests). Each is normalized against a saturation point, +weighted, and subtracted from 100. The weights are our assumption rather than a +published standard; they are declared in one place in the collector and the +sensitivity is something we intend to discuss rather than hide. + +Baseline results: + +| Measure | Value | +| --- | --- | +| Mean testability score | 75.87 | +| Least testable | `core.py` (20.36) | +| | `client.py` (36.67) | +| | `cli/argparser.py` (42.53) | +| | `output/writer.py` (46.17) | + +`core.py` is the clearest finding of the baseline. It scores 20.36 while the +project averages 75.87, it has the highest cyclomatic complexity in the codebase +(27), and it imports 15 internal modules plus 8 third-party ones. It is the +program entry point, so every end-to-end path passes through it, which means it +is simultaneously the hardest unit to isolate and the one most worth isolating. + +`cli/argparser.py` is the only module that lands in the bottom five on both +metrics, which makes it our primary candidate for deeper analysis. + +### Why these two metrics together + +They answer different questions and disagree in informative ways. MI asks how +hard code is to change; our testability score asks how hard it is to verify. +`output/writer.py` is acceptable on MI but poor on testability, because its +problem is coupling rather than internal complexity — a distinction MI alone +would hide. Tracking both lets us argue about which refactorings would actually +improve verifiability rather than just tidy the code. + +## Planned Direction + +With the baseline established, we intend to extract requirements and analyze the +existing test oracles, establish a baseline build and coverage run, and then +focus deeper testing work on the modules this baseline identified as weakest — +`core.py` and `cli/argparser.py` in particular. + +## Note on Reproducibility + +The figures above come from HTTPie v3.2.4. Our fork tracks `master`, so rerunning +the collector against the fork will shift the numbers slightly. All reported +results will be regenerated from the fork before the final report, using the +single command documented in `courseProjectCode/Metrics/README.md`. diff --git a/courseProjectDocs/requirements-and-oracles.md b/courseProjectDocs/requirements-and-oracles.md new file mode 100644 index 0000000000..e69de29bb2