forked from Rust-related/RustPython
Compare commits
2 Commits
v0.1.2
...
framestack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5b6b61fba | ||
|
|
ee86229ff6 |
@@ -1,19 +0,0 @@
|
|||||||
**/target/
|
|
||||||
**/*.rs.bk
|
|
||||||
**/*.bytecode
|
|
||||||
**/__pycache__/*
|
|
||||||
**/*.pytest_cache
|
|
||||||
.*sw*
|
|
||||||
.repl_history.txt
|
|
||||||
.vscode
|
|
||||||
wasm-pack.log
|
|
||||||
.idea/
|
|
||||||
tests/snippets/resources
|
|
||||||
|
|
||||||
flame-graph.html
|
|
||||||
flame.txt
|
|
||||||
flamescope.json
|
|
||||||
|
|
||||||
**/node_modules/
|
|
||||||
wasm/**/dist/
|
|
||||||
wasm/lib/pkg/
|
|
||||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
|||||||
Lib/* linguist-vendored
|
|
||||||
16
.github/ISSUE_TEMPLATE/report-incompatibility.md
vendored
16
.github/ISSUE_TEMPLATE/report-incompatibility.md
vendored
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: Report incompatibility
|
|
||||||
about: Report an incompatibility between RustPython and CPython
|
|
||||||
title: ''
|
|
||||||
labels: feat
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Feature
|
|
||||||
|
|
||||||
<!-- What Python feature is missing from RustPython? Give a short description of the feature and how you ran into its absence. -->
|
|
||||||
|
|
||||||
## Python Documentation
|
|
||||||
|
|
||||||
<!-- Give a link to the feature in the CPython documentation (https://docs.python.org/3/) in order to assist in its implementation. -->
|
|
||||||
24
.github/ISSUE_TEMPLATE/rfc.md
vendored
24
.github/ISSUE_TEMPLATE/rfc.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: RFC
|
|
||||||
about: Make a suggestion in a Request for Comments format to RustPython
|
|
||||||
title: "[RFC] "
|
|
||||||
labels: RFC
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
<!-- A quick overview of your suggestion -->
|
|
||||||
|
|
||||||
## Detailed Explanation
|
|
||||||
|
|
||||||
<!-- Elaborate on your suggestion in all its details -->
|
|
||||||
|
|
||||||
## Drawbacks, Rationale, and Alternatives
|
|
||||||
|
|
||||||
<!-- What drawbacks might this solution have? Why do you feel it is necessary? What other options might there be to solving this problem? -->
|
|
||||||
|
|
||||||
## Unresolved Questions
|
|
||||||
|
|
||||||
<!-- What would you like feedback on for fleshing out your suggestion? -->
|
|
||||||
161
.github/workflows/ci.yaml
vendored
161
.github/workflows/ci.yaml
vendored
@@ -1,161 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master, release]
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
name: CI
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_ARGS: --all --features ssl
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
rust_tests:
|
|
||||||
name: Run rust tests
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
||||||
fail-fast: false
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Set up the Windows environment
|
|
||||||
run: |
|
|
||||||
powershell.exe scripts/symlinks-to-hardlinks.ps1
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
- name: Cache cargo dependencies
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ runner.os }}-rust_tests-${{ hashFiles('Cargo.lock') }}
|
|
||||||
path: target
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-rust_tests-
|
|
||||||
- name: run rust tests
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: test
|
|
||||||
args: --verbose ${{ env.CARGO_ARGS }}
|
|
||||||
|
|
||||||
snippets_cpython:
|
|
||||||
name: Run snippets and cpython tests
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
||||||
fail-fast: false
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Set up the Windows environment
|
|
||||||
run: |
|
|
||||||
powershell.exe scripts/symlinks-to-hardlinks.ps1
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
- name: Cache cargo dependencies
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ runner.os }}-snippets-${{ hashFiles('Cargo.lock') }}
|
|
||||||
path: target
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-snippets-
|
|
||||||
- name: build rustpython
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --verbose ${{ env.CARGO_ARGS }}
|
|
||||||
- uses: actions/setup-python@v1
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: Install pipenv
|
|
||||||
run: |
|
|
||||||
python -V
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install pipenv
|
|
||||||
- run: pipenv install
|
|
||||||
working-directory: ./tests
|
|
||||||
- name: run snippets
|
|
||||||
run: pipenv run pytest -v
|
|
||||||
working-directory: ./tests
|
|
||||||
- name: run cpython tests
|
|
||||||
run: target/release/rustpython -m test -v
|
|
||||||
env:
|
|
||||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
|
|
||||||
format:
|
|
||||||
name: Check Rust code with rustfmt and clippy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
components: rustfmt
|
|
||||||
override: true
|
|
||||||
- name: run rustfmt
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: fmt
|
|
||||||
args: --all -- --check
|
|
||||||
- name: run clippy
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: clippy
|
|
||||||
args: ${{ env.CARGO_ARGS }} -- -Dwarnings
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Lint Python code with flake8
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- uses: actions/setup-python@v1
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: install flake8
|
|
||||||
run: python -m pip install flake8
|
|
||||||
- name: run lint
|
|
||||||
run: flake8 . --count --exclude=./.*,./Lib,./vm/Lib --select=E9,F63,F7,F82 --show-source --statistics
|
|
||||||
|
|
||||||
wasm:
|
|
||||||
name: Check the WASM package and demo
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: Cache cargo dependencies
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
key: ${{ runner.os }}-wasm-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
path: target
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-wasm-
|
|
||||||
- name: install wasm-pack
|
|
||||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
||||||
- name: install geckodriver
|
|
||||||
run: |
|
|
||||||
wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux32.tar.gz
|
|
||||||
mkdir geckodriver
|
|
||||||
tar -xzf geckodriver-v0.24.0-linux32.tar.gz -C geckodriver
|
|
||||||
- uses: actions/setup-python@v1
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: Install pipenv
|
|
||||||
run: |
|
|
||||||
python -V
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install pipenv
|
|
||||||
- run: pipenv install
|
|
||||||
working-directory: ./wasm/tests
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
- name: run test
|
|
||||||
run: |
|
|
||||||
export PATH=$PATH:`pwd`/../../geckodriver
|
|
||||||
npm install
|
|
||||||
npm run test
|
|
||||||
working-directory: ./wasm/demo
|
|
||||||
- name: Deploy demo to Github Pages
|
|
||||||
if: success() && github.ref == 'refs/heads/release'
|
|
||||||
uses: peaceiris/actions-gh-pages@v2
|
|
||||||
env:
|
|
||||||
ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEMO_DEPLOY_KEY }}
|
|
||||||
PUBLISH_DIR: ./wasm/demo/dist
|
|
||||||
EXTERNAL_REPOSITORY: RustPython/demo
|
|
||||||
PUBLISH_BRANCH: master
|
|
||||||
|
|
||||||
88
.github/workflows/cron-ci.yaml
vendored
88
.github/workflows/cron-ci.yaml
vendored
@@ -1,88 +0,0 @@
|
|||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 6'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
redox:
|
|
||||||
name: Check compilation on Redox
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: redoxos/redoxer:latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: prepare repository for redoxer compilation
|
|
||||||
run: bash scripts/redox/uncomment-cargo.sh
|
|
||||||
- name: compile for redox
|
|
||||||
run: redoxer build --verbose
|
|
||||||
|
|
||||||
codecov:
|
|
||||||
name: Collect code coverage data
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly
|
|
||||||
override: true
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --verbose
|
|
||||||
env:
|
|
||||||
CARGO_INCREMENTAL: '0'
|
|
||||||
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests' # -Cpanic=abort
|
|
||||||
- uses: actions/setup-python@v1
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
- name: Install pipenv
|
|
||||||
run: |
|
|
||||||
python -V
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install pipenv
|
|
||||||
- run: pipenv install
|
|
||||||
working-directory: ./tests
|
|
||||||
- name: run snippets
|
|
||||||
run: pipenv run pytest -v
|
|
||||||
working-directory: ./tests
|
|
||||||
env:
|
|
||||||
RUSTPYTHON_DEBUG: 'true'
|
|
||||||
- name: run cpython tests
|
|
||||||
run: cargo run -- -m test -v
|
|
||||||
env:
|
|
||||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
|
||||||
- uses: actions-rs/grcov@v0.1
|
|
||||||
id: coverage
|
|
||||||
- name: upload to Codecov
|
|
||||||
uses: codecov/codecov-action@v1
|
|
||||||
with:
|
|
||||||
file: ${{ steps.coverage.outputs.report }}
|
|
||||||
|
|
||||||
testdata:
|
|
||||||
name: Collect regression test data
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@master
|
|
||||||
- name: build rustpython
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: build
|
|
||||||
args: --release --verbose --all
|
|
||||||
- name: collect tests data
|
|
||||||
run: cargo run --release tests/jsontests.py
|
|
||||||
env:
|
|
||||||
RUSTPYTHONPATH: ${{ github.workspace }}/Lib
|
|
||||||
- name: upload tests data to the website
|
|
||||||
env:
|
|
||||||
SSHKEY: ${{ secrets.ACTIONS_TESTS_DATA_DEPLOY_KEY }}
|
|
||||||
GITHUB_ACTOR: ${{ github.actor }}
|
|
||||||
run: |
|
|
||||||
echo "$SSHKEY" >~/github_key
|
|
||||||
chmod 600 ~/github_key
|
|
||||||
export GIT_SSH_COMMAND="ssh -i ~/github_key"
|
|
||||||
|
|
||||||
git clone git@github.com:RustPython/rustpython.github.io.git website
|
|
||||||
cd website
|
|
||||||
cp ../tests/cpython_tests_results.json ./_data/regrtests_results.json
|
|
||||||
git add ./_data/regrtests_results.json
|
|
||||||
git -c user.name="Github Actions" -c user.email="actions@github.com" commit -m "Update regression test results" --author="$GITHUB_ACTOR"
|
|
||||||
git push
|
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,18 +1,8 @@
|
|||||||
/target
|
/target
|
||||||
/*/target
|
wasm/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
**/*.bytecode
|
**/*.bytecode
|
||||||
__pycache__
|
__pycache__
|
||||||
**/*.pytest_cache
|
**/*.pytest_cache
|
||||||
.*sw*
|
.*sw*
|
||||||
.repl_history.txt
|
.repl_history.txt
|
||||||
.vscode
|
|
||||||
wasm-pack.log
|
|
||||||
.idea/
|
|
||||||
tests/snippets/resources
|
|
||||||
|
|
||||||
flame-graph.html
|
|
||||||
flame.txt
|
|
||||||
flamescope.json
|
|
||||||
/wapm.lock
|
|
||||||
/wapm_packages
|
|
||||||
|
|||||||
14
.gitpod.Dockerfile
vendored
14
.gitpod.Dockerfile
vendored
@@ -1,14 +0,0 @@
|
|||||||
FROM gitpod/workspace-full
|
|
||||||
|
|
||||||
USER gitpod
|
|
||||||
|
|
||||||
# Update Rust to the latest version
|
|
||||||
RUN rm -rf ~/.rustup && \
|
|
||||||
export PATH=$HOME/.cargo/bin:$PATH && \
|
|
||||||
rustup update stable && \
|
|
||||||
rustup component add rls && \
|
|
||||||
# Set up wasm-pack and wasm32-unknown-unknown for rustpython_wasm
|
|
||||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh && \
|
|
||||||
rustup target add wasm32-unknown-unknown
|
|
||||||
|
|
||||||
USER root
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
image:
|
|
||||||
file: .gitpod.Dockerfile
|
|
||||||
66
.travis.yml
Normal file
66
.travis.yml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
language: rust
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
script:
|
||||||
|
- cargo build --verbose --all
|
||||||
|
- cargo test --verbose --all
|
||||||
|
|
||||||
|
env:
|
||||||
|
# This is used to only capture the regular nightly test in allow_failures
|
||||||
|
- REGULAR_TEST=true
|
||||||
|
|
||||||
|
cache: cargo
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# To test the snippets, we use Travis' Python environment (because
|
||||||
|
# installing rust ourselves is a lot easier than installing Python)
|
||||||
|
- language: python
|
||||||
|
python: 3.6
|
||||||
|
cache:
|
||||||
|
pip: true
|
||||||
|
# Because we're using the Python Travis environment, we can't use
|
||||||
|
# the built-in cargo cacher
|
||||||
|
directories:
|
||||||
|
- /home/travis/.cargo
|
||||||
|
- target
|
||||||
|
env:
|
||||||
|
- TRAVIS_RUST_VERSION=stable
|
||||||
|
- REGULAR_TEST=false
|
||||||
|
script: tests/.travis-runner.sh
|
||||||
|
- language: python
|
||||||
|
python: 3.6
|
||||||
|
cache:
|
||||||
|
pip: true
|
||||||
|
# Because we're using the Python Travis environment, we can't use
|
||||||
|
# the built-in cargo cacher
|
||||||
|
directories:
|
||||||
|
- /home/travis/.cargo
|
||||||
|
- target
|
||||||
|
env:
|
||||||
|
- TRAVIS_RUST_VERSION=beta
|
||||||
|
- REGULAR_TEST=false
|
||||||
|
script: tests/.travis-runner.sh
|
||||||
|
- name: rustfmt
|
||||||
|
language: rust
|
||||||
|
rust: nightly
|
||||||
|
cache: cargo
|
||||||
|
before_script:
|
||||||
|
- rustup component add rustfmt-preview
|
||||||
|
script:
|
||||||
|
# Code references the generated python.rs, so put something in
|
||||||
|
# place to make `cargo fmt` happy. (We use `echo` rather than
|
||||||
|
# `touch` because rustfmt complains about the empty file touch
|
||||||
|
# creates.)
|
||||||
|
- echo > parser/src/python.rs
|
||||||
|
- cargo fmt --all -- --check
|
||||||
|
env:
|
||||||
|
- REGULAR_TEST=false
|
||||||
|
allow_failures:
|
||||||
|
- rust: nightly
|
||||||
|
env: REGULAR_TEST=true
|
||||||
2332
Cargo.lock
generated
2332
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
56
Cargo.toml
56
Cargo.toml
@@ -1,54 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpython"
|
name = "rustpython"
|
||||||
version = "0.1.2"
|
version = "0.0.1"
|
||||||
authors = ["RustPython Team"]
|
authors = ["Windel Bouwman", "Shing Lyu <shing.lyu@gmail.com>"]
|
||||||
edition = "2018"
|
|
||||||
description = "A python interpreter written in rust."
|
|
||||||
repository = "https://github.com/RustPython/RustPython"
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "derive", "vm", "wasm/lib", "parser", "compiler", "bytecode", "examples/freeze"]
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "bench"
|
|
||||||
path = "./benchmarks/bench.rs"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
flame-it = ["rustpython-vm/flame-it", "flame", "flamescope"]
|
|
||||||
freeze-stdlib = ["rustpython-vm/freeze-stdlib"]
|
|
||||||
|
|
||||||
ssl = ["rustpython-vm/ssl"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log="0.4.1"
|
||||||
env_logger = "0.7"
|
env_logger="0.5.10"
|
||||||
clap = "2.33"
|
clap = "2.31.2"
|
||||||
rustpython-compiler = {path = "compiler", version = "0.1.1"}
|
rustpython_parser = {path = "parser"}
|
||||||
rustpython-parser = {path = "parser", version = "0.1.1"}
|
rustpython_vm = {path = "vm"}
|
||||||
rustpython-vm = {path = "vm", version = "0.1.1"}
|
rustyline = "2.1.0"
|
||||||
dirs = { package = "dirs-next", version = "1.0" }
|
|
||||||
num-traits = "0.2.8"
|
|
||||||
cfg-if = "0.1"
|
|
||||||
|
|
||||||
flame = { version = "0.2", optional = true }
|
|
||||||
flamescope = { version = "0.1", optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "wasi"))'.dependencies]
|
|
||||||
rustyline = "6.0"
|
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies.cpython]
|
|
||||||
version = "0.2"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "rustpython"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
# REDOX START, Uncommment when you want to compile/check with redoxer
|
|
||||||
# # following patches are just waiting on a new version to be released to crates.io
|
|
||||||
# nix = { git = "https://github.com/nix-rust/nix" }
|
|
||||||
# crossbeam-utils = { git = "https://github.com/crossbeam-rs/crossbeam" }
|
|
||||||
# socket2 = { git = "https://github.com/alexcrichton/socket2-rs" }
|
|
||||||
# REDOX END
|
|
||||||
|
|||||||
167
DEVELOPMENT.md
167
DEVELOPMENT.md
@@ -1,167 +0,0 @@
|
|||||||
# RustPython Development Guide and Tips
|
|
||||||
|
|
||||||
RustPython attracts developers with interest and experience in Rust, Python,
|
|
||||||
or WebAssembly. Whether you are familiar with Rust, Python, or
|
|
||||||
WebAssembly, the goal of this Development Guide is to give you the basics to
|
|
||||||
get set up for developing RustPython and contributing to this project.
|
|
||||||
|
|
||||||
The contents of the Development Guide include:
|
|
||||||
|
|
||||||
- [Setting up a development environment](#setting-up-a-development-environment)
|
|
||||||
- [Code style](#code-style)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Profiling](#profiling)
|
|
||||||
- [Code organization](#code-organization)
|
|
||||||
- [Understanding internals](#understanding-internals)
|
|
||||||
- [Questions](#questions)
|
|
||||||
|
|
||||||
## Setting up a development environment
|
|
||||||
|
|
||||||
RustPython requires the following:
|
|
||||||
|
|
||||||
- Rust latest stable version (e.g 1.38.0 at Oct 1st 2019)
|
|
||||||
- To check Rust version: `rustc --version`
|
|
||||||
- If you have `rustup` on your system, enter to update to the latest
|
|
||||||
stable version: `rustup update stable`
|
|
||||||
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
|
|
||||||
do so.
|
|
||||||
- CPython version 3.7.4 or higher
|
|
||||||
- CPython can be installed by your operating system's package manager,
|
|
||||||
from the [Python website](https://www.python.org/downloads/), or
|
|
||||||
using a third-party distribution, such as
|
|
||||||
[Anaconda](https://www.anaconda.com/distribution/).
|
|
||||||
- [Optional] The Python package, `pytest`, is used for testing Python code
|
|
||||||
snippets. To install, enter `python3 -m pip install pytest`.
|
|
||||||
|
|
||||||
## Code style
|
|
||||||
|
|
||||||
The Rust code style used is the default
|
|
||||||
[rustfmt](https://github.com/rust-lang/rustfmt) codestyle. Please format your
|
|
||||||
code accordingly. We also use [clippy](https://github.com/rust-lang/rust-clippy)
|
|
||||||
to detect rust code issues.
|
|
||||||
|
|
||||||
Python code should follow the
|
|
||||||
[PEP 8](https://www.python.org/dev/peps/pep-0008/) style. We also use
|
|
||||||
[flake8](http://flake8.pycqa.org/en/latest/) to check Python code style.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
To test RustPython's functionality, a collection of Python snippets is located
|
|
||||||
in the `tests/snippets` directory and can be run using `pytest`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cd tests
|
|
||||||
$ pytest -v
|
|
||||||
```
|
|
||||||
|
|
||||||
Rust unit tests can be run with `cargo`:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cargo test --all
|
|
||||||
```
|
|
||||||
|
|
||||||
## Profiling
|
|
||||||
|
|
||||||
To profile RustPython, build it in `release` mode with the `flame-it` feature.
|
|
||||||
This will generate a file `flamescope.json`, which can be viewed at
|
|
||||||
https://speedscope.app.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cargo run --release --features flame-it script.py
|
|
||||||
$ cat flamescope.json
|
|
||||||
{<json>}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can specify another file name other than the default by using the
|
|
||||||
`--output-file` option to specify a file name (or `stdout` if you specify `-`).
|
|
||||||
The `--output-format` option determines the format of the output file.
|
|
||||||
The speedscope json format (default), text, or raw html can be passed. There
|
|
||||||
exists a raw html viewer which is currently broken, and we welcome a PR to fix it.
|
|
||||||
|
|
||||||
## Code organization
|
|
||||||
|
|
||||||
Understanding a new codebase takes time. Here's a brief view of the
|
|
||||||
repository's structure:
|
|
||||||
|
|
||||||
- `bytecode/src`: python bytecode representation in rust structures
|
|
||||||
- `compiler/src`: python compilation to bytecode
|
|
||||||
- `derive/src`: Rust language extensions and macros specific to rustpython
|
|
||||||
- `parser/src`: python lexing, parsing and ast
|
|
||||||
- `Lib`: Carefully selected / copied files from CPython sourcecode. This is
|
|
||||||
the python side of the standard library.
|
|
||||||
- `vm/src`: python virtual machine
|
|
||||||
- `builtins.rs`: Builtin functions
|
|
||||||
- `compile.rs`: the python compiler from ast to bytecode
|
|
||||||
- `obj`: python builtin types
|
|
||||||
- `stdlib`: Standard library parts implemented in rust.
|
|
||||||
- `src`: using the other subcrates to bring rustpython to life.
|
|
||||||
- `docs`: documentation (work in progress)
|
|
||||||
- `py_code_object`: CPython bytecode to rustpython bytecode converter (work in
|
|
||||||
progress)
|
|
||||||
- `wasm`: Binary crate and resources for WebAssembly build
|
|
||||||
- `tests`: integration test snippets
|
|
||||||
|
|
||||||
## Understanding Internals
|
|
||||||
|
|
||||||
The RustPython workspace includes the `rustpython` top-level crate. The `Cargo.toml`
|
|
||||||
file in the root of the repo provide configuration of the crate and the
|
|
||||||
implementation is found in the `src` directory (specifically,
|
|
||||||
`src/main.rs`).
|
|
||||||
|
|
||||||
The top-level `rustpython` binary depends on several lower-level crates including:
|
|
||||||
|
|
||||||
- `rustpython-parser` (implementation in `parser/src`)
|
|
||||||
- `rustpython-compiler` (implementation in `compiler/src`)
|
|
||||||
- `rustpython-vm` (implementation in `vm/src`)
|
|
||||||
|
|
||||||
Together, these crates provide the functions of a programming language and
|
|
||||||
enable a line of code to go through a series of steps:
|
|
||||||
|
|
||||||
- parse the line of source code into tokens
|
|
||||||
- determine if the tokens are valid syntax
|
|
||||||
- create an Abstract Syntax Tree (AST)
|
|
||||||
- compile the AST into bytecode
|
|
||||||
- execute the bytecode in the virtual machine (VM).
|
|
||||||
|
|
||||||
### rustpython-parser
|
|
||||||
|
|
||||||
This crate contains the lexer and parser to convert a line of code to
|
|
||||||
an Abstract Syntax Tree (AST):
|
|
||||||
|
|
||||||
- Lexer: `parser/lexer.rs` converts Python source code into tokens
|
|
||||||
- Parser: `parser/parser.rs` takes the tokens generated by the lexer and parses
|
|
||||||
the tokens into an AST (Abstract Syntax Tree) where the nodes of the syntax
|
|
||||||
tree are Rust structs and enums.
|
|
||||||
- The Parser relies on `LALRPOP`, a Rust parser generator framework.
|
|
||||||
- More information on parsers and a tutorial can be found in the
|
|
||||||
[LALRPOP book](https://lalrpop.github.io/lalrpop/README.html).
|
|
||||||
- AST: `parser/ast.rs` implements in Rust the Python types and expressions
|
|
||||||
represented by the AST nodes.
|
|
||||||
|
|
||||||
### rustpython-compiler
|
|
||||||
|
|
||||||
The `rustpython-compiler` crate's purpose is to transform the AST (Abstract Syntax
|
|
||||||
Tree) to bytecode. The implementation of the compiler is found in the
|
|
||||||
`compiler/src` directory. The compiler implements Python's peephole optimizer
|
|
||||||
implementation, Symbol table, and streams in Rust.
|
|
||||||
|
|
||||||
Implementation of bytecode structure in Rust is found in the `bytecode/src`
|
|
||||||
directory. The `bytecode/src/bytecode.rs` contains the representation of
|
|
||||||
instructions and operations in Rust. Further information about Python's
|
|
||||||
bytecode instructions can be found in the
|
|
||||||
[Python documentation](https://docs.python.org/3/library/dis.html#bytecodes).
|
|
||||||
|
|
||||||
### rustpython-vm
|
|
||||||
|
|
||||||
The `rustpython-vm` crate has the important job of running the virtual machine that
|
|
||||||
executes Python's instructions. The `vm/src` directory contains code to
|
|
||||||
implement the read and evaluation loop that fetches and dispatches
|
|
||||||
instructions. This directory also contains the implementation of the
|
|
||||||
Python Standard Library modules in Rust (`vm/src/stdlib`). In Python
|
|
||||||
everything can be represented as an Object. `vm/src/obj` directory holds
|
|
||||||
the Rust code used to represent a Python Object and its methods.
|
|
||||||
|
|
||||||
## Questions
|
|
||||||
|
|
||||||
Have you tried these steps and have a question, please chat with us on
|
|
||||||
[gitter](https://gitter.im/rustpython/Lobby).
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
FROM rust:latest as rust
|
|
||||||
|
|
||||||
WORKDIR /rustpython
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN cargo build --release
|
|
||||||
|
|
||||||
FROM debian:stable-slim
|
|
||||||
|
|
||||||
COPY --from=rust /rustpython/target/release/rustpython /usr/bin
|
|
||||||
COPY --from=rust /rustpython/Lib /usr/lib/rustpython
|
|
||||||
ENV RUSTPYTHONPATH /usr/lib/rustpython
|
|
||||||
|
|
||||||
ENTRYPOINT [ "rustpython" ]
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
FROM rust:slim AS rust
|
|
||||||
|
|
||||||
WORKDIR /rustpython
|
|
||||||
|
|
||||||
USER root
|
|
||||||
ENV USER root
|
|
||||||
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install curl libssl-dev pkg-config -y && \
|
|
||||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN cd wasm/lib/ && wasm-pack build --release
|
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine AS node
|
|
||||||
|
|
||||||
WORKDIR /rustpython-demo
|
|
||||||
|
|
||||||
COPY --from=rust /rustpython/wasm/lib/pkg rustpython_wasm
|
|
||||||
|
|
||||||
COPY wasm/demo .
|
|
||||||
|
|
||||||
RUN npm install && npm run dist -- --env.noWasmPack --env.rustpythonPkg=rustpython_wasm
|
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
COPY --from=node /rustpython-demo/dist /usr/share/nginx/html
|
|
||||||
# Add the WASM mime type
|
|
||||||
RUN echo "types { application/wasm wasm; }" >>/etc/nginx/mime.types
|
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 RustPython Team
|
Copyright (c) 2018 Shing Lyu
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
395
LICENSE-logo
395
LICENSE-logo
@@ -1,395 +0,0 @@
|
|||||||
Attribution 4.0 International
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
||||||
does not provide legal services or legal advice. Distribution of
|
|
||||||
Creative Commons public licenses does not create a lawyer-client or
|
|
||||||
other relationship. Creative Commons makes its licenses and related
|
|
||||||
information available on an "as-is" basis. Creative Commons gives no
|
|
||||||
warranties regarding its licenses, any material licensed under their
|
|
||||||
terms and conditions, or any related information. Creative Commons
|
|
||||||
disclaims all liability for damages resulting from their use to the
|
|
||||||
fullest extent possible.
|
|
||||||
|
|
||||||
Using Creative Commons Public Licenses
|
|
||||||
|
|
||||||
Creative Commons public licenses provide a standard set of terms and
|
|
||||||
conditions that creators and other rights holders may use to share
|
|
||||||
original works of authorship and other material subject to copyright
|
|
||||||
and certain other rights specified in the public license below. The
|
|
||||||
following considerations are for informational purposes only, are not
|
|
||||||
exhaustive, and do not form part of our licenses.
|
|
||||||
|
|
||||||
Considerations for licensors: Our public licenses are
|
|
||||||
intended for use by those authorized to give the public
|
|
||||||
permission to use material in ways otherwise restricted by
|
|
||||||
copyright and certain other rights. Our licenses are
|
|
||||||
irrevocable. Licensors should read and understand the terms
|
|
||||||
and conditions of the license they choose before applying it.
|
|
||||||
Licensors should also secure all rights necessary before
|
|
||||||
applying our licenses so that the public can reuse the
|
|
||||||
material as expected. Licensors should clearly mark any
|
|
||||||
material not subject to the license. This includes other CC-
|
|
||||||
licensed material, or material used under an exception or
|
|
||||||
limitation to copyright. More considerations for licensors:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensors
|
|
||||||
|
|
||||||
Considerations for the public: By using one of our public
|
|
||||||
licenses, a licensor grants the public permission to use the
|
|
||||||
licensed material under specified terms and conditions. If
|
|
||||||
the licensor's permission is not necessary for any reason--for
|
|
||||||
example, because of any applicable exception or limitation to
|
|
||||||
copyright--then that use is not regulated by the license. Our
|
|
||||||
licenses grant only permissions under copyright and certain
|
|
||||||
other rights that a licensor has authority to grant. Use of
|
|
||||||
the licensed material may still be restricted for other
|
|
||||||
reasons, including because others have copyright or other
|
|
||||||
rights in the material. A licensor may make special requests,
|
|
||||||
such as asking that all changes be marked or described.
|
|
||||||
Although not required by our licenses, you are encouraged to
|
|
||||||
respect those requests where reasonable. More_considerations
|
|
||||||
for the public:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensees
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Attribution 4.0 International Public License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree
|
|
||||||
to be bound by the terms and conditions of this Creative Commons
|
|
||||||
Attribution 4.0 International Public License ("Public License"). To the
|
|
||||||
extent this Public License may be interpreted as a contract, You are
|
|
||||||
granted the Licensed Rights in consideration of Your acceptance of
|
|
||||||
these terms and conditions, and the Licensor grants You such rights in
|
|
||||||
consideration of benefits the Licensor receives from making the
|
|
||||||
Licensed Material available under these terms and conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Section 1 -- Definitions.
|
|
||||||
|
|
||||||
a. Adapted Material means material subject to Copyright and Similar
|
|
||||||
Rights that is derived from or based upon the Licensed Material
|
|
||||||
and in which the Licensed Material is translated, altered,
|
|
||||||
arranged, transformed, or otherwise modified in a manner requiring
|
|
||||||
permission under the Copyright and Similar Rights held by the
|
|
||||||
Licensor. For purposes of this Public License, where the Licensed
|
|
||||||
Material is a musical work, performance, or sound recording,
|
|
||||||
Adapted Material is always produced where the Licensed Material is
|
|
||||||
synched in timed relation with a moving image.
|
|
||||||
|
|
||||||
b. Adapter's License means the license You apply to Your Copyright
|
|
||||||
and Similar Rights in Your contributions to Adapted Material in
|
|
||||||
accordance with the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
|
||||||
closely related to copyright including, without limitation,
|
|
||||||
performance, broadcast, sound recording, and Sui Generis Database
|
|
||||||
Rights, without regard to how the rights are labeled or
|
|
||||||
categorized. For purposes of this Public License, the rights
|
|
||||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights.
|
|
||||||
|
|
||||||
d. Effective Technological Measures means those measures that, in the
|
|
||||||
absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
|
||||||
Treaty adopted on December 20, 1996, and/or similar international
|
|
||||||
agreements.
|
|
||||||
|
|
||||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
||||||
any other exception or limitation to Copyright and Similar Rights
|
|
||||||
that applies to Your use of the Licensed Material.
|
|
||||||
|
|
||||||
f. Licensed Material means the artistic or literary work, database,
|
|
||||||
or other material to which the Licensor applied this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
g. Licensed Rights means the rights granted to You subject to the
|
|
||||||
terms and conditions of this Public License, which are limited to
|
|
||||||
all Copyright and Similar Rights that apply to Your use of the
|
|
||||||
Licensed Material and that the Licensor has authority to license.
|
|
||||||
|
|
||||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
|
||||||
under this Public License.
|
|
||||||
|
|
||||||
i. Share means to provide material to the public by any means or
|
|
||||||
process that requires permission under the Licensed Rights, such
|
|
||||||
as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the
|
|
||||||
public may access the material from a place and at a time
|
|
||||||
individually chosen by them.
|
|
||||||
|
|
||||||
j. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of
|
|
||||||
the Council of 11 March 1996 on the legal protection of databases,
|
|
||||||
as amended and/or succeeded, as well as other essentially
|
|
||||||
equivalent rights anywhere in the world.
|
|
||||||
|
|
||||||
k. You means the individual or entity exercising the Licensed Rights
|
|
||||||
under this Public License. Your has a corresponding meaning.
|
|
||||||
|
|
||||||
|
|
||||||
Section 2 -- Scope.
|
|
||||||
|
|
||||||
a. License grant.
|
|
||||||
|
|
||||||
1. Subject to the terms and conditions of this Public License,
|
|
||||||
the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to
|
|
||||||
exercise the Licensed Rights in the Licensed Material to:
|
|
||||||
|
|
||||||
a. reproduce and Share the Licensed Material, in whole or
|
|
||||||
in part; and
|
|
||||||
|
|
||||||
b. produce, reproduce, and Share Adapted Material.
|
|
||||||
|
|
||||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
||||||
Exceptions and Limitations apply to Your use, this Public
|
|
||||||
License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions.
|
|
||||||
|
|
||||||
3. Term. The term of this Public License is specified in Section
|
|
||||||
6(a).
|
|
||||||
|
|
||||||
4. Media and formats; technical modifications allowed. The
|
|
||||||
Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created,
|
|
||||||
and to make technical modifications necessary to do so. The
|
|
||||||
Licensor waives and/or agrees not to assert any right or
|
|
||||||
authority to forbid You from making technical modifications
|
|
||||||
necessary to exercise the Licensed Rights, including
|
|
||||||
technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License,
|
|
||||||
simply making modifications authorized by this Section 2(a)
|
|
||||||
(4) never produces Adapted Material.
|
|
||||||
|
|
||||||
5. Downstream recipients.
|
|
||||||
|
|
||||||
a. Offer from the Licensor -- Licensed Material. Every
|
|
||||||
recipient of the Licensed Material automatically
|
|
||||||
receives an offer from the Licensor to exercise the
|
|
||||||
Licensed Rights under the terms and conditions of this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
b. No downstream restrictions. You may not offer or impose
|
|
||||||
any additional or different terms or conditions on, or
|
|
||||||
apply any Effective Technological Measures to, the
|
|
||||||
Licensed Material if doing so restricts exercise of the
|
|
||||||
Licensed Rights by any recipient of the Licensed
|
|
||||||
Material.
|
|
||||||
|
|
||||||
6. No endorsement. Nothing in this Public License constitutes or
|
|
||||||
may be construed as permission to assert or imply that You
|
|
||||||
are, or that Your use of the Licensed Material is, connected
|
|
||||||
with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as
|
|
||||||
provided in Section 3(a)(1)(A)(i).
|
|
||||||
|
|
||||||
b. Other rights.
|
|
||||||
|
|
||||||
1. Moral rights, such as the right of integrity, are not
|
|
||||||
licensed under this Public License, nor are publicity,
|
|
||||||
privacy, and/or other similar personality rights; however, to
|
|
||||||
the extent possible, the Licensor waives and/or agrees not to
|
|
||||||
assert any such rights held by the Licensor to the limited
|
|
||||||
extent necessary to allow You to exercise the Licensed
|
|
||||||
Rights, but not otherwise.
|
|
||||||
|
|
||||||
2. Patent and trademark rights are not licensed under this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
3. To the extent possible, the Licensor waives any right to
|
|
||||||
collect royalties from You for the exercise of the Licensed
|
|
||||||
Rights, whether directly or through a collecting society
|
|
||||||
under any voluntary or waivable statutory or compulsory
|
|
||||||
licensing scheme. In all other cases the Licensor expressly
|
|
||||||
reserves any right to collect such royalties.
|
|
||||||
|
|
||||||
|
|
||||||
Section 3 -- License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
a. Attribution.
|
|
||||||
|
|
||||||
1. If You Share the Licensed Material (including in modified
|
|
||||||
form), You must:
|
|
||||||
|
|
||||||
a. retain the following if it is supplied by the Licensor
|
|
||||||
with the Licensed Material:
|
|
||||||
|
|
||||||
i. identification of the creator(s) of the Licensed
|
|
||||||
Material and any others designated to receive
|
|
||||||
attribution, in any reasonable manner requested by
|
|
||||||
the Licensor (including by pseudonym if
|
|
||||||
designated);
|
|
||||||
|
|
||||||
ii. a copyright notice;
|
|
||||||
|
|
||||||
iii. a notice that refers to this Public License;
|
|
||||||
|
|
||||||
iv. a notice that refers to the disclaimer of
|
|
||||||
warranties;
|
|
||||||
|
|
||||||
v. a URI or hyperlink to the Licensed Material to the
|
|
||||||
extent reasonably practicable;
|
|
||||||
|
|
||||||
b. indicate if You modified the Licensed Material and
|
|
||||||
retain an indication of any previous modifications; and
|
|
||||||
|
|
||||||
c. indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or
|
|
||||||
hyperlink to, this Public License.
|
|
||||||
|
|
||||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
||||||
reasonable manner based on the medium, means, and context in
|
|
||||||
which You Share the Licensed Material. For example, it may be
|
|
||||||
reasonable to satisfy the conditions by providing a URI or
|
|
||||||
hyperlink to a resource that includes the required
|
|
||||||
information.
|
|
||||||
|
|
||||||
3. If requested by the Licensor, You must remove any of the
|
|
||||||
information required by Section 3(a)(1)(A) to the extent
|
|
||||||
reasonably practicable.
|
|
||||||
|
|
||||||
4. If You Share Adapted Material You produce, the Adapter's
|
|
||||||
License You apply must not prevent recipients of the Adapted
|
|
||||||
Material from complying with this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 4 -- Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that
|
|
||||||
apply to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
||||||
to extract, reuse, reproduce, and Share all or a substantial
|
|
||||||
portion of the contents of the database;
|
|
||||||
|
|
||||||
b. if You include all or a substantial portion of the database
|
|
||||||
contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database
|
|
||||||
Rights (but not its individual contents) is Adapted Material; and
|
|
||||||
|
|
||||||
c. You must comply with the conditions in Section 3(a) if You Share
|
|
||||||
all or a substantial portion of the contents of the database.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 4 supplements and does not
|
|
||||||
replace Your obligations under this Public License where the Licensed
|
|
||||||
Rights include other Copyright and Similar Rights.
|
|
||||||
|
|
||||||
|
|
||||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
||||||
|
|
||||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
||||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
||||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
||||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
||||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
||||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
||||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
||||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
||||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
||||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
||||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
||||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
||||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
||||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
||||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
||||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
c. The disclaimer of warranties and limitation of liability provided
|
|
||||||
above shall be interpreted in a manner that, to the extent
|
|
||||||
possible, most closely approximates an absolute disclaimer and
|
|
||||||
waiver of all liability.
|
|
||||||
|
|
||||||
|
|
||||||
Section 6 -- Term and Termination.
|
|
||||||
|
|
||||||
a. This Public License applies for the term of the Copyright and
|
|
||||||
Similar Rights licensed here. However, if You fail to comply with
|
|
||||||
this Public License, then Your rights under this Public License
|
|
||||||
terminate automatically.
|
|
||||||
|
|
||||||
b. Where Your right to use the Licensed Material has terminated under
|
|
||||||
Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
1. automatically as of the date the violation is cured, provided
|
|
||||||
it is cured within 30 days of Your discovery of the
|
|
||||||
violation; or
|
|
||||||
|
|
||||||
2. upon express reinstatement by the Licensor.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
|
||||||
right the Licensor may have to seek remedies for Your violations
|
|
||||||
of this Public License.
|
|
||||||
|
|
||||||
c. For the avoidance of doubt, the Licensor may also offer the
|
|
||||||
Licensed Material under separate terms or conditions or stop
|
|
||||||
distributing the Licensed Material at any time; however, doing so
|
|
||||||
will not terminate this Public License.
|
|
||||||
|
|
||||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 7 -- Other Terms and Conditions.
|
|
||||||
|
|
||||||
a. The Licensor shall not be bound by any additional or different
|
|
||||||
terms or conditions communicated by You unless expressly agreed.
|
|
||||||
|
|
||||||
b. Any arrangements, understandings, or agreements regarding the
|
|
||||||
Licensed Material not stated herein are separate from and
|
|
||||||
independent of the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 8 -- Interpretation.
|
|
||||||
|
|
||||||
a. For the avoidance of doubt, this Public License does not, and
|
|
||||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
|
||||||
conditions on any use of the Licensed Material that could lawfully
|
|
||||||
be made without permission under this Public License.
|
|
||||||
|
|
||||||
b. To the extent possible, if any provision of this Public License is
|
|
||||||
deemed unenforceable, it shall be automatically reformed to the
|
|
||||||
minimum extent necessary to make it enforceable. If the provision
|
|
||||||
cannot be reformed, it shall be severed from this Public License
|
|
||||||
without affecting the enforceability of the remaining terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
c. No term or condition of this Public License will be waived and no
|
|
||||||
failure to comply consented to unless expressly agreed to by the
|
|
||||||
Licensor.
|
|
||||||
|
|
||||||
d. Nothing in this Public License constitutes or may be interpreted
|
|
||||||
as a limitation upon, or waiver of, any privileges and immunities
|
|
||||||
that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority.
|
|
||||||
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons is not a party to its public
|
|
||||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
|
||||||
its public licenses to material it publishes and in those instances
|
|
||||||
will be considered the “Licensor.” The text of the Creative Commons
|
|
||||||
public licenses is dedicated to the public domain under the CC0 Public
|
|
||||||
Domain Dedication. Except for the limited purpose of indicating that
|
|
||||||
material is shared under a Creative Commons public license or as
|
|
||||||
otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark "Creative Commons" or any other trademark or logo
|
|
||||||
of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements,
|
|
||||||
understandings, or agreements concerning use of licensed material. For
|
|
||||||
the avoidance of doubt, this paragraph does not form part of the
|
|
||||||
public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
254
Lib/PSF-LICENSE
254
Lib/PSF-LICENSE
@@ -1,254 +0,0 @@
|
|||||||
A. HISTORY OF THE SOFTWARE
|
|
||||||
==========================
|
|
||||||
|
|
||||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
|
||||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
|
||||||
as a successor of a language called ABC. Guido remains Python's
|
|
||||||
principal author, although it includes many contributions from others.
|
|
||||||
|
|
||||||
In 1995, Guido continued his work on Python at the Corporation for
|
|
||||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
|
||||||
in Reston, Virginia where he released several versions of the
|
|
||||||
software.
|
|
||||||
|
|
||||||
In May 2000, Guido and the Python core development team moved to
|
|
||||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
|
||||||
year, the PythonLabs team moved to Digital Creations, which became
|
|
||||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
|
||||||
https://www.python.org/psf/) was formed, a non-profit organization
|
|
||||||
created specifically to own Python-related Intellectual Property.
|
|
||||||
Zope Corporation was a sponsoring member of the PSF.
|
|
||||||
|
|
||||||
All Python releases are Open Source (see http://www.opensource.org for
|
|
||||||
the Open Source Definition). Historically, most, but not all, Python
|
|
||||||
releases have also been GPL-compatible; the table below summarizes
|
|
||||||
the various releases.
|
|
||||||
|
|
||||||
Release Derived Year Owner GPL-
|
|
||||||
from compatible? (1)
|
|
||||||
|
|
||||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
|
||||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
|
||||||
1.6 1.5.2 2000 CNRI no
|
|
||||||
2.0 1.6 2000 BeOpen.com no
|
|
||||||
1.6.1 1.6 2001 CNRI yes (2)
|
|
||||||
2.1 2.0+1.6.1 2001 PSF no
|
|
||||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
|
||||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
|
||||||
2.1.2 2.1.1 2002 PSF yes
|
|
||||||
2.1.3 2.1.2 2002 PSF yes
|
|
||||||
2.2 and above 2.1.1 2001-now PSF yes
|
|
||||||
|
|
||||||
Footnotes:
|
|
||||||
|
|
||||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
|
||||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
|
||||||
a modified version without making your changes open source. The
|
|
||||||
GPL-compatible licenses make it possible to combine Python with
|
|
||||||
other software that is released under the GPL; the others don't.
|
|
||||||
|
|
||||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
|
||||||
because its license has a choice of law clause. According to
|
|
||||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
|
||||||
is "not incompatible" with the GPL.
|
|
||||||
|
|
||||||
Thanks to the many outside volunteers who have worked under Guido's
|
|
||||||
direction to make these releases possible.
|
|
||||||
|
|
||||||
|
|
||||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
|
||||||
===============================================================
|
|
||||||
|
|
||||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
|
||||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
|
||||||
otherwise using this software ("Python") in source or binary form and
|
|
||||||
its associated documentation.
|
|
||||||
|
|
||||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
|
||||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
|
||||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
|
||||||
distribute, and otherwise use Python alone or in any derivative version,
|
|
||||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
|
||||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
|
||||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All
|
|
||||||
Rights Reserved" are retained in Python alone or in any derivative version
|
|
||||||
prepared by Licensee.
|
|
||||||
|
|
||||||
3. In the event Licensee prepares a derivative work that is based on
|
|
||||||
or incorporates Python or any part thereof, and wants to make
|
|
||||||
the derivative work available to others as provided herein, then
|
|
||||||
Licensee hereby agrees to include in any such work a brief summary of
|
|
||||||
the changes made to Python.
|
|
||||||
|
|
||||||
4. PSF is making Python available to Licensee on an "AS IS"
|
|
||||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
||||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
|
||||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
|
||||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
|
||||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
|
||||||
|
|
||||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
|
||||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
|
||||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
|
||||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
|
||||||
|
|
||||||
6. This License Agreement will automatically terminate upon a material
|
|
||||||
breach of its terms and conditions.
|
|
||||||
|
|
||||||
7. Nothing in this License Agreement shall be deemed to create any
|
|
||||||
relationship of agency, partnership, or joint venture between PSF and
|
|
||||||
Licensee. This License Agreement does not grant permission to use PSF
|
|
||||||
trademarks or trade name in a trademark sense to endorse or promote
|
|
||||||
products or services of Licensee, or any third party.
|
|
||||||
|
|
||||||
8. By copying, installing or otherwise using Python, Licensee
|
|
||||||
agrees to be bound by the terms and conditions of this License
|
|
||||||
Agreement.
|
|
||||||
|
|
||||||
|
|
||||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
|
||||||
|
|
||||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
|
||||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
|
||||||
Individual or Organization ("Licensee") accessing and otherwise using
|
|
||||||
this software in source or binary form and its associated
|
|
||||||
documentation ("the Software").
|
|
||||||
|
|
||||||
2. Subject to the terms and conditions of this BeOpen Python License
|
|
||||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
|
||||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
|
||||||
and/or display publicly, prepare derivative works, distribute, and
|
|
||||||
otherwise use the Software alone or in any derivative version,
|
|
||||||
provided, however, that the BeOpen Python License is retained in the
|
|
||||||
Software, alone or in any derivative version prepared by Licensee.
|
|
||||||
|
|
||||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
|
||||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
||||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
|
||||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
|
||||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
|
||||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
|
||||||
|
|
||||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
|
||||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
|
||||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
|
||||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
|
||||||
|
|
||||||
5. This License Agreement will automatically terminate upon a material
|
|
||||||
breach of its terms and conditions.
|
|
||||||
|
|
||||||
6. This License Agreement shall be governed by and interpreted in all
|
|
||||||
respects by the law of the State of California, excluding conflict of
|
|
||||||
law provisions. Nothing in this License Agreement shall be deemed to
|
|
||||||
create any relationship of agency, partnership, or joint venture
|
|
||||||
between BeOpen and Licensee. This License Agreement does not grant
|
|
||||||
permission to use BeOpen trademarks or trade names in a trademark
|
|
||||||
sense to endorse or promote products or services of Licensee, or any
|
|
||||||
third party. As an exception, the "BeOpen Python" logos available at
|
|
||||||
http://www.pythonlabs.com/logos.html may be used according to the
|
|
||||||
permissions granted on that web page.
|
|
||||||
|
|
||||||
7. By copying, installing or otherwise using the software, Licensee
|
|
||||||
agrees to be bound by the terms and conditions of this License
|
|
||||||
Agreement.
|
|
||||||
|
|
||||||
|
|
||||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
|
||||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
|
||||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
|
||||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
|
||||||
source or binary form and its associated documentation.
|
|
||||||
|
|
||||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
|
||||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
|
||||||
license to reproduce, analyze, test, perform and/or display publicly,
|
|
||||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
|
||||||
alone or in any derivative version, provided, however, that CNRI's
|
|
||||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
|
||||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
|
||||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
|
||||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
|
||||||
Agreement, Licensee may substitute the following text (omitting the
|
|
||||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
|
||||||
conditions in CNRI's License Agreement. This Agreement together with
|
|
||||||
Python 1.6.1 may be located on the Internet using the following
|
|
||||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
|
||||||
Agreement may also be obtained from a proxy server on the Internet
|
|
||||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
|
||||||
|
|
||||||
3. In the event Licensee prepares a derivative work that is based on
|
|
||||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
|
||||||
the derivative work available to others as provided herein, then
|
|
||||||
Licensee hereby agrees to include in any such work a brief summary of
|
|
||||||
the changes made to Python 1.6.1.
|
|
||||||
|
|
||||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
|
||||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
||||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
|
||||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
|
||||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
|
||||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
|
||||||
|
|
||||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
|
||||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
|
||||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
|
||||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
|
||||||
|
|
||||||
6. This License Agreement will automatically terminate upon a material
|
|
||||||
breach of its terms and conditions.
|
|
||||||
|
|
||||||
7. This License Agreement shall be governed by the federal
|
|
||||||
intellectual property law of the United States, including without
|
|
||||||
limitation the federal copyright law, and, to the extent such
|
|
||||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
|
||||||
Virginia, excluding Virginia's conflict of law provisions.
|
|
||||||
Notwithstanding the foregoing, with regard to derivative works based
|
|
||||||
on Python 1.6.1 that incorporate non-separable material that was
|
|
||||||
previously distributed under the GNU General Public License (GPL), the
|
|
||||||
law of the Commonwealth of Virginia shall govern this License
|
|
||||||
Agreement only as to issues arising under or with respect to
|
|
||||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
|
||||||
License Agreement shall be deemed to create any relationship of
|
|
||||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
|
||||||
License Agreement does not grant permission to use CNRI trademarks or
|
|
||||||
trade name in a trademark sense to endorse or promote products or
|
|
||||||
services of Licensee, or any third party.
|
|
||||||
|
|
||||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
|
||||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
|
||||||
bound by the terms and conditions of this License Agreement.
|
|
||||||
|
|
||||||
ACCEPT
|
|
||||||
|
|
||||||
|
|
||||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
|
||||||
--------------------------------------------------
|
|
||||||
|
|
||||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
|
||||||
The Netherlands. All rights reserved.
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software and its
|
|
||||||
documentation for any purpose and without fee is hereby granted,
|
|
||||||
provided that the above copyright notice appear in all copies and that
|
|
||||||
both that copyright notice and this permission notice appear in
|
|
||||||
supporting documentation, and that the name of Stichting Mathematisch
|
|
||||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
|
||||||
distribution of the software without specific, written prior
|
|
||||||
permission.
|
|
||||||
|
|
||||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
|
||||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
|
||||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
||||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Standard Library for RustPython
|
|
||||||
|
|
||||||
This directory contains all of the Python files that make up the standard
|
|
||||||
library for RustPython.
|
|
||||||
|
|
||||||
Most of these files are copied over from the CPython repository (the 3.7
|
|
||||||
branch), with slight modifications to allow them to work under RustPython. The
|
|
||||||
current goal is to complete the standard library with as few modifications as
|
|
||||||
possible. Current modifications are just temporary workarounds for bugs/missing
|
|
||||||
feature within the RustPython implementation.
|
|
||||||
|
|
||||||
The first big module we are targeting is `unittest`, so we can leverage the
|
|
||||||
CPython test suite.
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
"""Record of phased-in incompatible language changes.
|
|
||||||
|
|
||||||
Each line is of the form:
|
|
||||||
|
|
||||||
FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease ","
|
|
||||||
CompilerFlag ")"
|
|
||||||
|
|
||||||
where, normally, OptionalRelease < MandatoryRelease, and both are 5-tuples
|
|
||||||
of the same form as sys.version_info:
|
|
||||||
|
|
||||||
(PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int
|
|
||||||
PY_MINOR_VERSION, # the 1; an int
|
|
||||||
PY_MICRO_VERSION, # the 0; an int
|
|
||||||
PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string
|
|
||||||
PY_RELEASE_SERIAL # the 3; an int
|
|
||||||
)
|
|
||||||
|
|
||||||
OptionalRelease records the first release in which
|
|
||||||
|
|
||||||
from __future__ import FeatureName
|
|
||||||
|
|
||||||
was accepted.
|
|
||||||
|
|
||||||
In the case of MandatoryReleases that have not yet occurred,
|
|
||||||
MandatoryRelease predicts the release in which the feature will become part
|
|
||||||
of the language.
|
|
||||||
|
|
||||||
Else MandatoryRelease records when the feature became part of the language;
|
|
||||||
in releases at or after that, modules no longer need
|
|
||||||
|
|
||||||
from __future__ import FeatureName
|
|
||||||
|
|
||||||
to use the feature in question, but may continue to use such imports.
|
|
||||||
|
|
||||||
MandatoryRelease may also be None, meaning that a planned feature got
|
|
||||||
dropped.
|
|
||||||
|
|
||||||
Instances of class _Feature have two corresponding methods,
|
|
||||||
.getOptionalRelease() and .getMandatoryRelease().
|
|
||||||
|
|
||||||
CompilerFlag is the (bitfield) flag that should be passed in the fourth
|
|
||||||
argument to the builtin function compile() to enable the feature in
|
|
||||||
dynamically compiled code. This flag is stored in the .compiler_flag
|
|
||||||
attribute on _Future instances. These values must match the appropriate
|
|
||||||
#defines of CO_xxx flags in Include/compile.h.
|
|
||||||
|
|
||||||
No feature line is ever to be deleted from this file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
all_feature_names = [
|
|
||||||
"nested_scopes",
|
|
||||||
"generators",
|
|
||||||
"division",
|
|
||||||
"absolute_import",
|
|
||||||
"with_statement",
|
|
||||||
"print_function",
|
|
||||||
"unicode_literals",
|
|
||||||
"barry_as_FLUFL",
|
|
||||||
"generator_stop",
|
|
||||||
]
|
|
||||||
|
|
||||||
__all__ = ["all_feature_names"] + all_feature_names
|
|
||||||
|
|
||||||
# The CO_xxx symbols are defined here under the same names used by
|
|
||||||
# compile.h, so that an editor search will find them here. However,
|
|
||||||
# they're not exported in __all__, because they don't really belong to
|
|
||||||
# this module.
|
|
||||||
CO_NESTED = 0x0010 # nested_scopes
|
|
||||||
CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
|
|
||||||
CO_FUTURE_DIVISION = 0x2000 # division
|
|
||||||
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default
|
|
||||||
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
|
|
||||||
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
|
|
||||||
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
|
|
||||||
CO_FUTURE_BARRY_AS_BDFL = 0x40000
|
|
||||||
CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators
|
|
||||||
|
|
||||||
class _Feature:
|
|
||||||
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
|
|
||||||
self.optional = optionalRelease
|
|
||||||
self.mandatory = mandatoryRelease
|
|
||||||
self.compiler_flag = compiler_flag
|
|
||||||
|
|
||||||
def getOptionalRelease(self):
|
|
||||||
"""Return first release in which this feature was recognized.
|
|
||||||
|
|
||||||
This is a 5-tuple, of the same form as sys.version_info.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.optional
|
|
||||||
|
|
||||||
def getMandatoryRelease(self):
|
|
||||||
"""Return release in which this feature will become mandatory.
|
|
||||||
|
|
||||||
This is a 5-tuple, of the same form as sys.version_info, or, if
|
|
||||||
the feature was dropped, is None.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.mandatory
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "_Feature" + repr((self.optional,
|
|
||||||
self.mandatory,
|
|
||||||
self.compiler_flag))
|
|
||||||
|
|
||||||
nested_scopes = _Feature((2, 1, 0, "beta", 1),
|
|
||||||
(2, 2, 0, "alpha", 0),
|
|
||||||
CO_NESTED)
|
|
||||||
|
|
||||||
generators = _Feature((2, 2, 0, "alpha", 1),
|
|
||||||
(2, 3, 0, "final", 0),
|
|
||||||
CO_GENERATOR_ALLOWED)
|
|
||||||
|
|
||||||
division = _Feature((2, 2, 0, "alpha", 2),
|
|
||||||
(3, 0, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_DIVISION)
|
|
||||||
|
|
||||||
absolute_import = _Feature((2, 5, 0, "alpha", 1),
|
|
||||||
(3, 0, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_ABSOLUTE_IMPORT)
|
|
||||||
|
|
||||||
with_statement = _Feature((2, 5, 0, "alpha", 1),
|
|
||||||
(2, 6, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_WITH_STATEMENT)
|
|
||||||
|
|
||||||
print_function = _Feature((2, 6, 0, "alpha", 2),
|
|
||||||
(3, 0, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_PRINT_FUNCTION)
|
|
||||||
|
|
||||||
unicode_literals = _Feature((2, 6, 0, "alpha", 2),
|
|
||||||
(3, 0, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_UNICODE_LITERALS)
|
|
||||||
|
|
||||||
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
|
|
||||||
(3, 9, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_BARRY_AS_BDFL)
|
|
||||||
|
|
||||||
generator_stop = _Feature((3, 5, 0, "beta", 1),
|
|
||||||
(3, 7, 0, "alpha", 0),
|
|
||||||
CO_FUTURE_GENERATOR_STOP)
|
|
||||||
1676
Lib/_codecs.py
1676
Lib/_codecs.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,251 +0,0 @@
|
|||||||
# This module is used to map the old Python 2 names to the new names used in
|
|
||||||
# Python 3 for the pickle module. This needed to make pickle streams
|
|
||||||
# generated with Python 2 loadable by Python 3.
|
|
||||||
|
|
||||||
# This is a copy of lib2to3.fixes.fix_imports.MAPPING. We cannot import
|
|
||||||
# lib2to3 and use the mapping defined there, because lib2to3 uses pickle.
|
|
||||||
# Thus, this could cause the module to be imported recursively.
|
|
||||||
IMPORT_MAPPING = {
|
|
||||||
'__builtin__' : 'builtins',
|
|
||||||
'copy_reg': 'copyreg',
|
|
||||||
'Queue': 'queue',
|
|
||||||
'SocketServer': 'socketserver',
|
|
||||||
'ConfigParser': 'configparser',
|
|
||||||
'repr': 'reprlib',
|
|
||||||
'tkFileDialog': 'tkinter.filedialog',
|
|
||||||
'tkSimpleDialog': 'tkinter.simpledialog',
|
|
||||||
'tkColorChooser': 'tkinter.colorchooser',
|
|
||||||
'tkCommonDialog': 'tkinter.commondialog',
|
|
||||||
'Dialog': 'tkinter.dialog',
|
|
||||||
'Tkdnd': 'tkinter.dnd',
|
|
||||||
'tkFont': 'tkinter.font',
|
|
||||||
'tkMessageBox': 'tkinter.messagebox',
|
|
||||||
'ScrolledText': 'tkinter.scrolledtext',
|
|
||||||
'Tkconstants': 'tkinter.constants',
|
|
||||||
'Tix': 'tkinter.tix',
|
|
||||||
'ttk': 'tkinter.ttk',
|
|
||||||
'Tkinter': 'tkinter',
|
|
||||||
'markupbase': '_markupbase',
|
|
||||||
'_winreg': 'winreg',
|
|
||||||
'thread': '_thread',
|
|
||||||
'dummy_thread': '_dummy_thread',
|
|
||||||
'dbhash': 'dbm.bsd',
|
|
||||||
'dumbdbm': 'dbm.dumb',
|
|
||||||
'dbm': 'dbm.ndbm',
|
|
||||||
'gdbm': 'dbm.gnu',
|
|
||||||
'xmlrpclib': 'xmlrpc.client',
|
|
||||||
'SimpleXMLRPCServer': 'xmlrpc.server',
|
|
||||||
'httplib': 'http.client',
|
|
||||||
'htmlentitydefs' : 'html.entities',
|
|
||||||
'HTMLParser' : 'html.parser',
|
|
||||||
'Cookie': 'http.cookies',
|
|
||||||
'cookielib': 'http.cookiejar',
|
|
||||||
'BaseHTTPServer': 'http.server',
|
|
||||||
'test.test_support': 'test.support',
|
|
||||||
'commands': 'subprocess',
|
|
||||||
'urlparse' : 'urllib.parse',
|
|
||||||
'robotparser' : 'urllib.robotparser',
|
|
||||||
'urllib2': 'urllib.request',
|
|
||||||
'anydbm': 'dbm',
|
|
||||||
'_abcoll' : 'collections.abc',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# This contains rename rules that are easy to handle. We ignore the more
|
|
||||||
# complex stuff (e.g. mapping the names in the urllib and types modules).
|
|
||||||
# These rules should be run before import names are fixed.
|
|
||||||
NAME_MAPPING = {
|
|
||||||
('__builtin__', 'xrange'): ('builtins', 'range'),
|
|
||||||
('__builtin__', 'reduce'): ('functools', 'reduce'),
|
|
||||||
('__builtin__', 'intern'): ('sys', 'intern'),
|
|
||||||
('__builtin__', 'unichr'): ('builtins', 'chr'),
|
|
||||||
('__builtin__', 'unicode'): ('builtins', 'str'),
|
|
||||||
('__builtin__', 'long'): ('builtins', 'int'),
|
|
||||||
('itertools', 'izip'): ('builtins', 'zip'),
|
|
||||||
('itertools', 'imap'): ('builtins', 'map'),
|
|
||||||
('itertools', 'ifilter'): ('builtins', 'filter'),
|
|
||||||
('itertools', 'ifilterfalse'): ('itertools', 'filterfalse'),
|
|
||||||
('itertools', 'izip_longest'): ('itertools', 'zip_longest'),
|
|
||||||
('UserDict', 'IterableUserDict'): ('collections', 'UserDict'),
|
|
||||||
('UserList', 'UserList'): ('collections', 'UserList'),
|
|
||||||
('UserString', 'UserString'): ('collections', 'UserString'),
|
|
||||||
('whichdb', 'whichdb'): ('dbm', 'whichdb'),
|
|
||||||
('_socket', 'fromfd'): ('socket', 'fromfd'),
|
|
||||||
('_multiprocessing', 'Connection'): ('multiprocessing.connection', 'Connection'),
|
|
||||||
('multiprocessing.process', 'Process'): ('multiprocessing.context', 'Process'),
|
|
||||||
('multiprocessing.forking', 'Popen'): ('multiprocessing.popen_fork', 'Popen'),
|
|
||||||
('urllib', 'ContentTooShortError'): ('urllib.error', 'ContentTooShortError'),
|
|
||||||
('urllib', 'getproxies'): ('urllib.request', 'getproxies'),
|
|
||||||
('urllib', 'pathname2url'): ('urllib.request', 'pathname2url'),
|
|
||||||
('urllib', 'quote_plus'): ('urllib.parse', 'quote_plus'),
|
|
||||||
('urllib', 'quote'): ('urllib.parse', 'quote'),
|
|
||||||
('urllib', 'unquote_plus'): ('urllib.parse', 'unquote_plus'),
|
|
||||||
('urllib', 'unquote'): ('urllib.parse', 'unquote'),
|
|
||||||
('urllib', 'url2pathname'): ('urllib.request', 'url2pathname'),
|
|
||||||
('urllib', 'urlcleanup'): ('urllib.request', 'urlcleanup'),
|
|
||||||
('urllib', 'urlencode'): ('urllib.parse', 'urlencode'),
|
|
||||||
('urllib', 'urlopen'): ('urllib.request', 'urlopen'),
|
|
||||||
('urllib', 'urlretrieve'): ('urllib.request', 'urlretrieve'),
|
|
||||||
('urllib2', 'HTTPError'): ('urllib.error', 'HTTPError'),
|
|
||||||
('urllib2', 'URLError'): ('urllib.error', 'URLError'),
|
|
||||||
}
|
|
||||||
|
|
||||||
PYTHON2_EXCEPTIONS = (
|
|
||||||
"ArithmeticError",
|
|
||||||
"AssertionError",
|
|
||||||
"AttributeError",
|
|
||||||
"BaseException",
|
|
||||||
"BufferError",
|
|
||||||
"BytesWarning",
|
|
||||||
"DeprecationWarning",
|
|
||||||
"EOFError",
|
|
||||||
"EnvironmentError",
|
|
||||||
"Exception",
|
|
||||||
"FloatingPointError",
|
|
||||||
"FutureWarning",
|
|
||||||
"GeneratorExit",
|
|
||||||
"IOError",
|
|
||||||
"ImportError",
|
|
||||||
"ImportWarning",
|
|
||||||
"IndentationError",
|
|
||||||
"IndexError",
|
|
||||||
"KeyError",
|
|
||||||
"KeyboardInterrupt",
|
|
||||||
"LookupError",
|
|
||||||
"MemoryError",
|
|
||||||
"NameError",
|
|
||||||
"NotImplementedError",
|
|
||||||
"OSError",
|
|
||||||
"OverflowError",
|
|
||||||
"PendingDeprecationWarning",
|
|
||||||
"ReferenceError",
|
|
||||||
"RuntimeError",
|
|
||||||
"RuntimeWarning",
|
|
||||||
# StandardError is gone in Python 3, so we map it to Exception
|
|
||||||
"StopIteration",
|
|
||||||
"SyntaxError",
|
|
||||||
"SyntaxWarning",
|
|
||||||
"SystemError",
|
|
||||||
"SystemExit",
|
|
||||||
"TabError",
|
|
||||||
"TypeError",
|
|
||||||
"UnboundLocalError",
|
|
||||||
"UnicodeDecodeError",
|
|
||||||
"UnicodeEncodeError",
|
|
||||||
"UnicodeError",
|
|
||||||
"UnicodeTranslateError",
|
|
||||||
"UnicodeWarning",
|
|
||||||
"UserWarning",
|
|
||||||
"ValueError",
|
|
||||||
"Warning",
|
|
||||||
"ZeroDivisionError",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
WindowsError
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
PYTHON2_EXCEPTIONS += ("WindowsError",)
|
|
||||||
|
|
||||||
for excname in PYTHON2_EXCEPTIONS:
|
|
||||||
NAME_MAPPING[("exceptions", excname)] = ("builtins", excname)
|
|
||||||
|
|
||||||
MULTIPROCESSING_EXCEPTIONS = (
|
|
||||||
'AuthenticationError',
|
|
||||||
'BufferTooShort',
|
|
||||||
'ProcessError',
|
|
||||||
'TimeoutError',
|
|
||||||
)
|
|
||||||
|
|
||||||
for excname in MULTIPROCESSING_EXCEPTIONS:
|
|
||||||
NAME_MAPPING[("multiprocessing", excname)] = ("multiprocessing.context", excname)
|
|
||||||
|
|
||||||
# Same, but for 3.x to 2.x
|
|
||||||
REVERSE_IMPORT_MAPPING = dict((v, k) for (k, v) in IMPORT_MAPPING.items())
|
|
||||||
assert len(REVERSE_IMPORT_MAPPING) == len(IMPORT_MAPPING)
|
|
||||||
REVERSE_NAME_MAPPING = dict((v, k) for (k, v) in NAME_MAPPING.items())
|
|
||||||
assert len(REVERSE_NAME_MAPPING) == len(NAME_MAPPING)
|
|
||||||
|
|
||||||
# Non-mutual mappings.
|
|
||||||
|
|
||||||
IMPORT_MAPPING.update({
|
|
||||||
'cPickle': 'pickle',
|
|
||||||
'_elementtree': 'xml.etree.ElementTree',
|
|
||||||
'FileDialog': 'tkinter.filedialog',
|
|
||||||
'SimpleDialog': 'tkinter.simpledialog',
|
|
||||||
'DocXMLRPCServer': 'xmlrpc.server',
|
|
||||||
'SimpleHTTPServer': 'http.server',
|
|
||||||
'CGIHTTPServer': 'http.server',
|
|
||||||
# For compatibility with broken pickles saved in old Python 3 versions
|
|
||||||
'UserDict': 'collections',
|
|
||||||
'UserList': 'collections',
|
|
||||||
'UserString': 'collections',
|
|
||||||
'whichdb': 'dbm',
|
|
||||||
'StringIO': 'io',
|
|
||||||
'cStringIO': 'io',
|
|
||||||
})
|
|
||||||
|
|
||||||
REVERSE_IMPORT_MAPPING.update({
|
|
||||||
'_bz2': 'bz2',
|
|
||||||
'_dbm': 'dbm',
|
|
||||||
'_functools': 'functools',
|
|
||||||
'_gdbm': 'gdbm',
|
|
||||||
'_pickle': 'pickle',
|
|
||||||
})
|
|
||||||
|
|
||||||
NAME_MAPPING.update({
|
|
||||||
('__builtin__', 'basestring'): ('builtins', 'str'),
|
|
||||||
('exceptions', 'StandardError'): ('builtins', 'Exception'),
|
|
||||||
('UserDict', 'UserDict'): ('collections', 'UserDict'),
|
|
||||||
('socket', '_socketobject'): ('socket', 'SocketType'),
|
|
||||||
})
|
|
||||||
|
|
||||||
REVERSE_NAME_MAPPING.update({
|
|
||||||
('_functools', 'reduce'): ('__builtin__', 'reduce'),
|
|
||||||
('tkinter.filedialog', 'FileDialog'): ('FileDialog', 'FileDialog'),
|
|
||||||
('tkinter.filedialog', 'LoadFileDialog'): ('FileDialog', 'LoadFileDialog'),
|
|
||||||
('tkinter.filedialog', 'SaveFileDialog'): ('FileDialog', 'SaveFileDialog'),
|
|
||||||
('tkinter.simpledialog', 'SimpleDialog'): ('SimpleDialog', 'SimpleDialog'),
|
|
||||||
('xmlrpc.server', 'ServerHTMLDoc'): ('DocXMLRPCServer', 'ServerHTMLDoc'),
|
|
||||||
('xmlrpc.server', 'XMLRPCDocGenerator'):
|
|
||||||
('DocXMLRPCServer', 'XMLRPCDocGenerator'),
|
|
||||||
('xmlrpc.server', 'DocXMLRPCRequestHandler'):
|
|
||||||
('DocXMLRPCServer', 'DocXMLRPCRequestHandler'),
|
|
||||||
('xmlrpc.server', 'DocXMLRPCServer'):
|
|
||||||
('DocXMLRPCServer', 'DocXMLRPCServer'),
|
|
||||||
('xmlrpc.server', 'DocCGIXMLRPCRequestHandler'):
|
|
||||||
('DocXMLRPCServer', 'DocCGIXMLRPCRequestHandler'),
|
|
||||||
('http.server', 'SimpleHTTPRequestHandler'):
|
|
||||||
('SimpleHTTPServer', 'SimpleHTTPRequestHandler'),
|
|
||||||
('http.server', 'CGIHTTPRequestHandler'):
|
|
||||||
('CGIHTTPServer', 'CGIHTTPRequestHandler'),
|
|
||||||
('_socket', 'socket'): ('socket', '_socketobject'),
|
|
||||||
})
|
|
||||||
|
|
||||||
PYTHON3_OSERROR_EXCEPTIONS = (
|
|
||||||
'BrokenPipeError',
|
|
||||||
'ChildProcessError',
|
|
||||||
'ConnectionAbortedError',
|
|
||||||
'ConnectionError',
|
|
||||||
'ConnectionRefusedError',
|
|
||||||
'ConnectionResetError',
|
|
||||||
'FileExistsError',
|
|
||||||
'FileNotFoundError',
|
|
||||||
'InterruptedError',
|
|
||||||
'IsADirectoryError',
|
|
||||||
'NotADirectoryError',
|
|
||||||
'PermissionError',
|
|
||||||
'ProcessLookupError',
|
|
||||||
'TimeoutError',
|
|
||||||
)
|
|
||||||
|
|
||||||
for excname in PYTHON3_OSERROR_EXCEPTIONS:
|
|
||||||
REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'OSError')
|
|
||||||
|
|
||||||
PYTHON3_IMPORTERROR_EXCEPTIONS = (
|
|
||||||
'ModuleNotFoundError',
|
|
||||||
)
|
|
||||||
|
|
||||||
for excname in PYTHON3_IMPORTERROR_EXCEPTIONS:
|
|
||||||
REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'ImportError')
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
"""Drop-in replacement for the thread module.
|
|
||||||
|
|
||||||
Meant to be used as a brain-dead substitute so that threaded code does
|
|
||||||
not need to be rewritten for when the thread module is not present.
|
|
||||||
|
|
||||||
Suggested usage is::
|
|
||||||
|
|
||||||
try:
|
|
||||||
import _thread
|
|
||||||
except ImportError:
|
|
||||||
import _dummy_thread as _thread
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Exports only things specified by thread documentation;
|
|
||||||
# skipping obsolete synonyms allocate(), start_new(), exit_thread().
|
|
||||||
__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
|
|
||||||
'interrupt_main', 'LockType']
|
|
||||||
|
|
||||||
# A dummy value
|
|
||||||
TIMEOUT_MAX = 2**31
|
|
||||||
|
|
||||||
# NOTE: this module can be imported early in the extension building process,
|
|
||||||
# and so top level imports of other modules should be avoided. Instead, all
|
|
||||||
# imports are done when needed on a function-by-function basis. Since threads
|
|
||||||
# are disabled, the import lock should not be an issue anyway (??).
|
|
||||||
|
|
||||||
error = RuntimeError
|
|
||||||
|
|
||||||
def start_new_thread(function, args, kwargs={}):
|
|
||||||
"""Dummy implementation of _thread.start_new_thread().
|
|
||||||
|
|
||||||
Compatibility is maintained by making sure that ``args`` is a
|
|
||||||
tuple and ``kwargs`` is a dictionary. If an exception is raised
|
|
||||||
and it is SystemExit (which can be done by _thread.exit()) it is
|
|
||||||
caught and nothing is done; all other exceptions are printed out
|
|
||||||
by using traceback.print_exc().
|
|
||||||
|
|
||||||
If the executed function calls interrupt_main the KeyboardInterrupt will be
|
|
||||||
raised when the function returns.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if type(args) != type(tuple()):
|
|
||||||
raise TypeError("2nd arg must be a tuple")
|
|
||||||
if type(kwargs) != type(dict()):
|
|
||||||
raise TypeError("3rd arg must be a dict")
|
|
||||||
global _main
|
|
||||||
_main = False
|
|
||||||
try:
|
|
||||||
function(*args, **kwargs)
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
_main = True
|
|
||||||
global _interrupt
|
|
||||||
if _interrupt:
|
|
||||||
_interrupt = False
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
|
|
||||||
def exit():
|
|
||||||
"""Dummy implementation of _thread.exit()."""
|
|
||||||
raise SystemExit
|
|
||||||
|
|
||||||
def get_ident():
|
|
||||||
"""Dummy implementation of _thread.get_ident().
|
|
||||||
|
|
||||||
Since this module should only be used when _threadmodule is not
|
|
||||||
available, it is safe to assume that the current process is the
|
|
||||||
only thread. Thus a constant can be safely returned.
|
|
||||||
"""
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def allocate_lock():
|
|
||||||
"""Dummy implementation of _thread.allocate_lock()."""
|
|
||||||
return LockType()
|
|
||||||
|
|
||||||
def stack_size(size=None):
|
|
||||||
"""Dummy implementation of _thread.stack_size()."""
|
|
||||||
if size is not None:
|
|
||||||
raise error("setting thread stack size not supported")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _set_sentinel():
|
|
||||||
"""Dummy implementation of _thread._set_sentinel()."""
|
|
||||||
return LockType()
|
|
||||||
|
|
||||||
class LockType(object):
|
|
||||||
"""Class implementing dummy implementation of _thread.LockType.
|
|
||||||
|
|
||||||
Compatibility is maintained by maintaining self.locked_status
|
|
||||||
which is a boolean that stores the state of the lock. Pickling of
|
|
||||||
the lock, though, should not be done since if the _thread module is
|
|
||||||
then used with an unpickled ``lock()`` from here problems could
|
|
||||||
occur from this class not having atomic methods.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.locked_status = False
|
|
||||||
|
|
||||||
def acquire(self, waitflag=None, timeout=-1):
|
|
||||||
"""Dummy implementation of acquire().
|
|
||||||
|
|
||||||
For blocking calls, self.locked_status is automatically set to
|
|
||||||
True and returned appropriately based on value of
|
|
||||||
``waitflag``. If it is non-blocking, then the value is
|
|
||||||
actually checked and not set if it is already acquired. This
|
|
||||||
is all done so that threading.Condition's assert statements
|
|
||||||
aren't triggered and throw a little fit.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if waitflag is None or waitflag:
|
|
||||||
self.locked_status = True
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
if not self.locked_status:
|
|
||||||
self.locked_status = True
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
if timeout > 0:
|
|
||||||
import time
|
|
||||||
time.sleep(timeout)
|
|
||||||
return False
|
|
||||||
|
|
||||||
__enter__ = acquire
|
|
||||||
|
|
||||||
def __exit__(self, typ, val, tb):
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
"""Release the dummy lock."""
|
|
||||||
# XXX Perhaps shouldn't actually bother to test? Could lead
|
|
||||||
# to problems for complex, threaded code.
|
|
||||||
if not self.locked_status:
|
|
||||||
raise error
|
|
||||||
self.locked_status = False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def locked(self):
|
|
||||||
return self.locked_status
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s %s.%s object at %s>" % (
|
|
||||||
"locked" if self.locked_status else "unlocked",
|
|
||||||
self.__class__.__module__,
|
|
||||||
self.__class__.__qualname__,
|
|
||||||
hex(id(self))
|
|
||||||
)
|
|
||||||
|
|
||||||
# Used to signal that interrupt_main was called in a "thread"
|
|
||||||
_interrupt = False
|
|
||||||
# True when not executing in a "thread"
|
|
||||||
_main = True
|
|
||||||
|
|
||||||
def interrupt_main():
|
|
||||||
"""Set _interrupt flag to True to have start_new_thread raise
|
|
||||||
KeyboardInterrupt upon exiting."""
|
|
||||||
if _main:
|
|
||||||
raise KeyboardInterrupt
|
|
||||||
else:
|
|
||||||
global _interrupt
|
|
||||||
_interrupt = True
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
"""Shared support for scanning document type declarations in HTML and XHTML.
|
|
||||||
|
|
||||||
This module is used as a foundation for the html.parser module. It has no
|
|
||||||
documented public API and should not be used directly.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
|
|
||||||
_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
|
|
||||||
_commentclose = re.compile(r'--\s*>')
|
|
||||||
_markedsectionclose = re.compile(r']\s*]\s*>')
|
|
||||||
|
|
||||||
# An analysis of the MS-Word extensions is available at
|
|
||||||
# http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf
|
|
||||||
|
|
||||||
_msmarkedsectionclose = re.compile(r']\s*>')
|
|
||||||
|
|
||||||
del re
|
|
||||||
|
|
||||||
|
|
||||||
class ParserBase:
|
|
||||||
"""Parser base class which provides some common support methods used
|
|
||||||
by the SGML/HTML and XHTML parsers."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if self.__class__ is ParserBase:
|
|
||||||
raise RuntimeError(
|
|
||||||
"_markupbase.ParserBase must be subclassed")
|
|
||||||
|
|
||||||
def error(self, message):
|
|
||||||
raise NotImplementedError(
|
|
||||||
"subclasses of ParserBase must override error()")
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.lineno = 1
|
|
||||||
self.offset = 0
|
|
||||||
|
|
||||||
def getpos(self):
|
|
||||||
"""Return current line number and offset."""
|
|
||||||
return self.lineno, self.offset
|
|
||||||
|
|
||||||
# Internal -- update line number and offset. This should be
|
|
||||||
# called for each piece of data exactly once, in order -- in other
|
|
||||||
# words the concatenation of all the input strings to this
|
|
||||||
# function should be exactly the entire input.
|
|
||||||
def updatepos(self, i, j):
|
|
||||||
if i >= j:
|
|
||||||
return j
|
|
||||||
rawdata = self.rawdata
|
|
||||||
nlines = rawdata.count("\n", i, j)
|
|
||||||
if nlines:
|
|
||||||
self.lineno = self.lineno + nlines
|
|
||||||
pos = rawdata.rindex("\n", i, j) # Should not fail
|
|
||||||
self.offset = j-(pos+1)
|
|
||||||
else:
|
|
||||||
self.offset = self.offset + j-i
|
|
||||||
return j
|
|
||||||
|
|
||||||
_decl_otherchars = ''
|
|
||||||
|
|
||||||
# Internal -- parse declaration (for use by subclasses).
|
|
||||||
def parse_declaration(self, i):
|
|
||||||
# This is some sort of declaration; in "HTML as
|
|
||||||
# deployed," this should only be the document type
|
|
||||||
# declaration ("<!DOCTYPE html...>").
|
|
||||||
# ISO 8879:1986, however, has more complex
|
|
||||||
# declaration syntax for elements in <!...>, including:
|
|
||||||
# --comment--
|
|
||||||
# [marked section]
|
|
||||||
# name in the following list: ENTITY, DOCTYPE, ELEMENT,
|
|
||||||
# ATTLIST, NOTATION, SHORTREF, USEMAP,
|
|
||||||
# LINKTYPE, LINK, IDLINK, USELINK, SYSTEM
|
|
||||||
rawdata = self.rawdata
|
|
||||||
j = i + 2
|
|
||||||
assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
|
|
||||||
if rawdata[j:j+1] == ">":
|
|
||||||
# the empty comment <!>
|
|
||||||
return j + 1
|
|
||||||
if rawdata[j:j+1] in ("-", ""):
|
|
||||||
# Start of comment followed by buffer boundary,
|
|
||||||
# or just a buffer boundary.
|
|
||||||
return -1
|
|
||||||
# A simple, practical version could look like: ((name|stringlit) S*) + '>'
|
|
||||||
n = len(rawdata)
|
|
||||||
if rawdata[j:j+2] == '--': #comment
|
|
||||||
# Locate --.*-- as the body of the comment
|
|
||||||
return self.parse_comment(i)
|
|
||||||
elif rawdata[j] == '[': #marked section
|
|
||||||
# Locate [statusWord [...arbitrary SGML...]] as the body of the marked section
|
|
||||||
# Where statusWord is one of TEMP, CDATA, IGNORE, INCLUDE, RCDATA
|
|
||||||
# Note that this is extended by Microsoft Office "Save as Web" function
|
|
||||||
# to include [if...] and [endif].
|
|
||||||
return self.parse_marked_section(i)
|
|
||||||
else: #all other declaration elements
|
|
||||||
decltype, j = self._scan_name(j, i)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
if decltype == "doctype":
|
|
||||||
self._decl_otherchars = ''
|
|
||||||
while j < n:
|
|
||||||
c = rawdata[j]
|
|
||||||
if c == ">":
|
|
||||||
# end of declaration syntax
|
|
||||||
data = rawdata[i+2:j]
|
|
||||||
if decltype == "doctype":
|
|
||||||
self.handle_decl(data)
|
|
||||||
else:
|
|
||||||
# According to the HTML5 specs sections "8.2.4.44 Bogus
|
|
||||||
# comment state" and "8.2.4.45 Markup declaration open
|
|
||||||
# state", a comment token should be emitted.
|
|
||||||
# Calling unknown_decl provides more flexibility though.
|
|
||||||
self.unknown_decl(data)
|
|
||||||
return j + 1
|
|
||||||
if c in "\"'":
|
|
||||||
m = _declstringlit_match(rawdata, j)
|
|
||||||
if not m:
|
|
||||||
return -1 # incomplete
|
|
||||||
j = m.end()
|
|
||||||
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
|
|
||||||
name, j = self._scan_name(j, i)
|
|
||||||
elif c in self._decl_otherchars:
|
|
||||||
j = j + 1
|
|
||||||
elif c == "[":
|
|
||||||
# this could be handled in a separate doctype parser
|
|
||||||
if decltype == "doctype":
|
|
||||||
j = self._parse_doctype_subset(j + 1, i)
|
|
||||||
elif decltype in {"attlist", "linktype", "link", "element"}:
|
|
||||||
# must tolerate []'d groups in a content model in an element declaration
|
|
||||||
# also in data attribute specifications of attlist declaration
|
|
||||||
# also link type declaration subsets in linktype declarations
|
|
||||||
# also link attribute specification lists in link declarations
|
|
||||||
self.error("unsupported '[' char in %s declaration" % decltype)
|
|
||||||
else:
|
|
||||||
self.error("unexpected '[' char in declaration")
|
|
||||||
else:
|
|
||||||
self.error(
|
|
||||||
"unexpected %r char in declaration" % rawdata[j])
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
return -1 # incomplete
|
|
||||||
|
|
||||||
# Internal -- parse a marked section
|
|
||||||
# Override this to handle MS-word extension syntax <![if word]>content<![endif]>
|
|
||||||
def parse_marked_section(self, i, report=1):
|
|
||||||
rawdata= self.rawdata
|
|
||||||
assert rawdata[i:i+3] == '<![', "unexpected call to parse_marked_section()"
|
|
||||||
sectName, j = self._scan_name( i+3, i )
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
if sectName in {"temp", "cdata", "ignore", "include", "rcdata"}:
|
|
||||||
# look for standard ]]> ending
|
|
||||||
match= _markedsectionclose.search(rawdata, i+3)
|
|
||||||
elif sectName in {"if", "else", "endif"}:
|
|
||||||
# look for MS Office ]> ending
|
|
||||||
match= _msmarkedsectionclose.search(rawdata, i+3)
|
|
||||||
else:
|
|
||||||
self.error('unknown status keyword %r in marked section' % rawdata[i+3:j])
|
|
||||||
if not match:
|
|
||||||
return -1
|
|
||||||
if report:
|
|
||||||
j = match.start(0)
|
|
||||||
self.unknown_decl(rawdata[i+3: j])
|
|
||||||
return match.end(0)
|
|
||||||
|
|
||||||
# Internal -- parse comment, return length or -1 if not terminated
|
|
||||||
def parse_comment(self, i, report=1):
|
|
||||||
rawdata = self.rawdata
|
|
||||||
if rawdata[i:i+4] != '<!--':
|
|
||||||
self.error('unexpected call to parse_comment()')
|
|
||||||
match = _commentclose.search(rawdata, i+4)
|
|
||||||
if not match:
|
|
||||||
return -1
|
|
||||||
if report:
|
|
||||||
j = match.start(0)
|
|
||||||
self.handle_comment(rawdata[i+4: j])
|
|
||||||
return match.end(0)
|
|
||||||
|
|
||||||
# Internal -- scan past the internal subset in a <!DOCTYPE declaration,
|
|
||||||
# returning the index just past any whitespace following the trailing ']'.
|
|
||||||
def _parse_doctype_subset(self, i, declstartpos):
|
|
||||||
rawdata = self.rawdata
|
|
||||||
n = len(rawdata)
|
|
||||||
j = i
|
|
||||||
while j < n:
|
|
||||||
c = rawdata[j]
|
|
||||||
if c == "<":
|
|
||||||
s = rawdata[j:j+2]
|
|
||||||
if s == "<":
|
|
||||||
# end of buffer; incomplete
|
|
||||||
return -1
|
|
||||||
if s != "<!":
|
|
||||||
self.updatepos(declstartpos, j + 1)
|
|
||||||
self.error("unexpected char in internal subset (in %r)" % s)
|
|
||||||
if (j + 2) == n:
|
|
||||||
# end of buffer; incomplete
|
|
||||||
return -1
|
|
||||||
if (j + 4) > n:
|
|
||||||
# end of buffer; incomplete
|
|
||||||
return -1
|
|
||||||
if rawdata[j:j+4] == "<!--":
|
|
||||||
j = self.parse_comment(j, report=0)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
continue
|
|
||||||
name, j = self._scan_name(j + 2, declstartpos)
|
|
||||||
if j == -1:
|
|
||||||
return -1
|
|
||||||
if name not in {"attlist", "element", "entity", "notation"}:
|
|
||||||
self.updatepos(declstartpos, j + 2)
|
|
||||||
self.error(
|
|
||||||
"unknown declaration %r in internal subset" % name)
|
|
||||||
# handle the individual names
|
|
||||||
meth = getattr(self, "_parse_doctype_" + name)
|
|
||||||
j = meth(j, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
elif c == "%":
|
|
||||||
# parameter entity reference
|
|
||||||
if (j + 1) == n:
|
|
||||||
# end of buffer; incomplete
|
|
||||||
return -1
|
|
||||||
s, j = self._scan_name(j + 1, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
if rawdata[j] == ";":
|
|
||||||
j = j + 1
|
|
||||||
elif c == "]":
|
|
||||||
j = j + 1
|
|
||||||
while j < n and rawdata[j].isspace():
|
|
||||||
j = j + 1
|
|
||||||
if j < n:
|
|
||||||
if rawdata[j] == ">":
|
|
||||||
return j
|
|
||||||
self.updatepos(declstartpos, j)
|
|
||||||
self.error("unexpected char after internal subset")
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
elif c.isspace():
|
|
||||||
j = j + 1
|
|
||||||
else:
|
|
||||||
self.updatepos(declstartpos, j)
|
|
||||||
self.error("unexpected char %r in internal subset" % c)
|
|
||||||
# end of buffer reached
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# Internal -- scan past <!ELEMENT declarations
|
|
||||||
def _parse_doctype_element(self, i, declstartpos):
|
|
||||||
name, j = self._scan_name(i, declstartpos)
|
|
||||||
if j == -1:
|
|
||||||
return -1
|
|
||||||
# style content model; just skip until '>'
|
|
||||||
rawdata = self.rawdata
|
|
||||||
if '>' in rawdata[j:]:
|
|
||||||
return rawdata.find(">", j) + 1
|
|
||||||
return -1
|
|
||||||
|
|
||||||
# Internal -- scan past <!ATTLIST declarations
|
|
||||||
def _parse_doctype_attlist(self, i, declstartpos):
|
|
||||||
rawdata = self.rawdata
|
|
||||||
name, j = self._scan_name(i, declstartpos)
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if c == "":
|
|
||||||
return -1
|
|
||||||
if c == ">":
|
|
||||||
return j + 1
|
|
||||||
while 1:
|
|
||||||
# scan a series of attribute descriptions; simplified:
|
|
||||||
# name type [value] [#constraint]
|
|
||||||
name, j = self._scan_name(j, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if c == "":
|
|
||||||
return -1
|
|
||||||
if c == "(":
|
|
||||||
# an enumerated type; look for ')'
|
|
||||||
if ")" in rawdata[j:]:
|
|
||||||
j = rawdata.find(")", j) + 1
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
while rawdata[j:j+1].isspace():
|
|
||||||
j = j + 1
|
|
||||||
if not rawdata[j:]:
|
|
||||||
# end of buffer, incomplete
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
name, j = self._scan_name(j, declstartpos)
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
return -1
|
|
||||||
if c in "'\"":
|
|
||||||
m = _declstringlit_match(rawdata, j)
|
|
||||||
if m:
|
|
||||||
j = m.end()
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
return -1
|
|
||||||
if c == "#":
|
|
||||||
if rawdata[j:] == "#":
|
|
||||||
# end of buffer
|
|
||||||
return -1
|
|
||||||
name, j = self._scan_name(j + 1, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
return -1
|
|
||||||
if c == '>':
|
|
||||||
# all done
|
|
||||||
return j + 1
|
|
||||||
|
|
||||||
# Internal -- scan past <!NOTATION declarations
|
|
||||||
def _parse_doctype_notation(self, i, declstartpos):
|
|
||||||
name, j = self._scan_name(i, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
rawdata = self.rawdata
|
|
||||||
while 1:
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
# end of buffer; incomplete
|
|
||||||
return -1
|
|
||||||
if c == '>':
|
|
||||||
return j + 1
|
|
||||||
if c in "'\"":
|
|
||||||
m = _declstringlit_match(rawdata, j)
|
|
||||||
if not m:
|
|
||||||
return -1
|
|
||||||
j = m.end()
|
|
||||||
else:
|
|
||||||
name, j = self._scan_name(j, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
|
|
||||||
# Internal -- scan past <!ENTITY declarations
|
|
||||||
def _parse_doctype_entity(self, i, declstartpos):
|
|
||||||
rawdata = self.rawdata
|
|
||||||
if rawdata[i:i+1] == "%":
|
|
||||||
j = i + 1
|
|
||||||
while 1:
|
|
||||||
c = rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
return -1
|
|
||||||
if c.isspace():
|
|
||||||
j = j + 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
j = i
|
|
||||||
name, j = self._scan_name(j, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
while 1:
|
|
||||||
c = self.rawdata[j:j+1]
|
|
||||||
if not c:
|
|
||||||
return -1
|
|
||||||
if c in "'\"":
|
|
||||||
m = _declstringlit_match(rawdata, j)
|
|
||||||
if m:
|
|
||||||
j = m.end()
|
|
||||||
else:
|
|
||||||
return -1 # incomplete
|
|
||||||
elif c == ">":
|
|
||||||
return j + 1
|
|
||||||
else:
|
|
||||||
name, j = self._scan_name(j, declstartpos)
|
|
||||||
if j < 0:
|
|
||||||
return j
|
|
||||||
|
|
||||||
# Internal -- scan a name token and the new position and the token, or
|
|
||||||
# return -1 if we've reached the end of the buffer.
|
|
||||||
def _scan_name(self, i, declstartpos):
|
|
||||||
rawdata = self.rawdata
|
|
||||||
n = len(rawdata)
|
|
||||||
if i == n:
|
|
||||||
return None, -1
|
|
||||||
m = _declname_match(rawdata, i)
|
|
||||||
if m:
|
|
||||||
s = m.group()
|
|
||||||
name = s.strip()
|
|
||||||
if (i + len(s)) == n:
|
|
||||||
return None, -1 # end of buffer
|
|
||||||
return name.lower(), m.end()
|
|
||||||
else:
|
|
||||||
self.updatepos(declstartpos, i)
|
|
||||||
self.error("expected name token at %r"
|
|
||||||
% rawdata[declstartpos:declstartpos+20])
|
|
||||||
|
|
||||||
# To be overridden -- handlers for unknown objects
|
|
||||||
def unknown_decl(self, data):
|
|
||||||
pass
|
|
||||||
147
Lib/_py_abc.py
147
Lib/_py_abc.py
@@ -1,147 +0,0 @@
|
|||||||
from _weakrefset import WeakSet
|
|
||||||
|
|
||||||
|
|
||||||
def get_cache_token():
|
|
||||||
"""Returns the current ABC cache token.
|
|
||||||
|
|
||||||
The token is an opaque object (supporting equality testing) identifying the
|
|
||||||
current version of the ABC cache for virtual subclasses. The token changes
|
|
||||||
with every call to ``register()`` on any ABC.
|
|
||||||
"""
|
|
||||||
return ABCMeta._abc_invalidation_counter
|
|
||||||
|
|
||||||
|
|
||||||
class ABCMeta(type):
|
|
||||||
"""Metaclass for defining Abstract Base Classes (ABCs).
|
|
||||||
|
|
||||||
Use this metaclass to create an ABC. An ABC can be subclassed
|
|
||||||
directly, and then acts as a mix-in class. You can also register
|
|
||||||
unrelated concrete classes (even built-in classes) and unrelated
|
|
||||||
ABCs as 'virtual subclasses' -- these and their descendants will
|
|
||||||
be considered subclasses of the registering ABC by the built-in
|
|
||||||
issubclass() function, but the registering ABC won't show up in
|
|
||||||
their MRO (Method Resolution Order) nor will method
|
|
||||||
implementations defined by the registering ABC be callable (not
|
|
||||||
even via super()).
|
|
||||||
"""
|
|
||||||
|
|
||||||
# A global counter that is incremented each time a class is
|
|
||||||
# registered as a virtual subclass of anything. It forces the
|
|
||||||
# negative cache to be cleared before its next use.
|
|
||||||
# Note: this counter is private. Use `abc.get_cache_token()` for
|
|
||||||
# external code.
|
|
||||||
_abc_invalidation_counter = 0
|
|
||||||
|
|
||||||
def __new__(mcls, name, bases, namespace, **kwargs):
|
|
||||||
cls = type.__new__(mcls, name, bases, namespace, **kwargs)
|
|
||||||
# Compute set of abstract method names
|
|
||||||
abstracts = {name
|
|
||||||
for name, value in namespace.items()
|
|
||||||
if getattr(value, "__isabstractmethod__", False)}
|
|
||||||
for base in bases:
|
|
||||||
for name in getattr(base, "__abstractmethods__", set()):
|
|
||||||
value = getattr(cls, name, None)
|
|
||||||
if getattr(value, "__isabstractmethod__", False):
|
|
||||||
abstracts.add(name)
|
|
||||||
cls.__abstractmethods__ = set(abstracts)
|
|
||||||
# Set up inheritance registry
|
|
||||||
cls._abc_registry = WeakSet()
|
|
||||||
cls._abc_cache = WeakSet()
|
|
||||||
cls._abc_negative_cache = WeakSet()
|
|
||||||
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
|
|
||||||
return cls
|
|
||||||
|
|
||||||
def register(cls, subclass):
|
|
||||||
"""Register a virtual subclass of an ABC.
|
|
||||||
|
|
||||||
Returns the subclass, to allow usage as a class decorator.
|
|
||||||
"""
|
|
||||||
if not isinstance(subclass, type):
|
|
||||||
raise TypeError("Can only register classes")
|
|
||||||
if issubclass(subclass, cls):
|
|
||||||
return subclass # Already a subclass
|
|
||||||
# Subtle: test for cycles *after* testing for "already a subclass";
|
|
||||||
# this means we allow X.register(X) and interpret it as a no-op.
|
|
||||||
if issubclass(cls, subclass):
|
|
||||||
# This would create a cycle, which is bad for the algorithm below
|
|
||||||
raise RuntimeError("Refusing to create an inheritance cycle")
|
|
||||||
cls._abc_registry.add(subclass)
|
|
||||||
ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache
|
|
||||||
return subclass
|
|
||||||
|
|
||||||
def _dump_registry(cls, file=None):
|
|
||||||
"""Debug helper to print the ABC registry."""
|
|
||||||
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
|
|
||||||
print(f"Inv. counter: {get_cache_token()}", file=file)
|
|
||||||
for name in cls.__dict__:
|
|
||||||
if name.startswith("_abc_"):
|
|
||||||
value = getattr(cls, name)
|
|
||||||
if isinstance(value, WeakSet):
|
|
||||||
value = set(value)
|
|
||||||
print(f"{name}: {value!r}", file=file)
|
|
||||||
|
|
||||||
def _abc_registry_clear(cls):
|
|
||||||
"""Clear the registry (for debugging or testing)."""
|
|
||||||
cls._abc_registry.clear()
|
|
||||||
|
|
||||||
def _abc_caches_clear(cls):
|
|
||||||
"""Clear the caches (for debugging or testing)."""
|
|
||||||
cls._abc_cache.clear()
|
|
||||||
cls._abc_negative_cache.clear()
|
|
||||||
|
|
||||||
def __instancecheck__(cls, instance):
|
|
||||||
"""Override for isinstance(instance, cls)."""
|
|
||||||
# Inline the cache checking
|
|
||||||
subclass = instance.__class__
|
|
||||||
if subclass in cls._abc_cache:
|
|
||||||
return True
|
|
||||||
subtype = type(instance)
|
|
||||||
if subtype is subclass:
|
|
||||||
if (cls._abc_negative_cache_version ==
|
|
||||||
ABCMeta._abc_invalidation_counter and
|
|
||||||
subclass in cls._abc_negative_cache):
|
|
||||||
return False
|
|
||||||
# Fall back to the subclass check.
|
|
||||||
return cls.__subclasscheck__(subclass)
|
|
||||||
return any(cls.__subclasscheck__(c) for c in (subclass, subtype))
|
|
||||||
|
|
||||||
def __subclasscheck__(cls, subclass):
|
|
||||||
"""Override for issubclass(subclass, cls)."""
|
|
||||||
if not isinstance(subclass, type):
|
|
||||||
raise TypeError('issubclass() arg 1 must be a class')
|
|
||||||
# Check cache
|
|
||||||
if subclass in cls._abc_cache:
|
|
||||||
return True
|
|
||||||
# Check negative cache; may have to invalidate
|
|
||||||
if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter:
|
|
||||||
# Invalidate the negative cache
|
|
||||||
cls._abc_negative_cache = WeakSet()
|
|
||||||
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
|
|
||||||
elif subclass in cls._abc_negative_cache:
|
|
||||||
return False
|
|
||||||
# Check the subclass hook
|
|
||||||
ok = cls.__subclasshook__(subclass)
|
|
||||||
if ok is not NotImplemented:
|
|
||||||
assert isinstance(ok, bool)
|
|
||||||
if ok:
|
|
||||||
cls._abc_cache.add(subclass)
|
|
||||||
else:
|
|
||||||
cls._abc_negative_cache.add(subclass)
|
|
||||||
return ok
|
|
||||||
# Check if it's a direct subclass
|
|
||||||
if cls in getattr(subclass, '__mro__', ()):
|
|
||||||
cls._abc_cache.add(subclass)
|
|
||||||
return True
|
|
||||||
# Check if it's a subclass of a registered class (recursive)
|
|
||||||
for rcls in cls._abc_registry:
|
|
||||||
if issubclass(subclass, rcls):
|
|
||||||
cls._abc_cache.add(subclass)
|
|
||||||
return True
|
|
||||||
# Check if it's a subclass of a subclass (recursive)
|
|
||||||
for scls in cls.__subclasses__():
|
|
||||||
if issubclass(subclass, scls):
|
|
||||||
cls._abc_cache.add(subclass)
|
|
||||||
return True
|
|
||||||
# No dice; update negative cache
|
|
||||||
cls._abc_negative_cache.add(subclass)
|
|
||||||
return False
|
|
||||||
6449
Lib/_pydecimal.py
6449
Lib/_pydecimal.py
File diff suppressed because it is too large
Load Diff
@@ -1,104 +0,0 @@
|
|||||||
"""
|
|
||||||
The objects used by the site module to add custom builtins.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Those objects are almost immortal and they keep a reference to their module
|
|
||||||
# globals. Defining them in the site module would keep too many references
|
|
||||||
# alive.
|
|
||||||
# Note this means this module should also avoid keep things alive in its
|
|
||||||
# globals.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
class Quitter(object):
|
|
||||||
def __init__(self, name, eof):
|
|
||||||
self.name = name
|
|
||||||
self.eof = eof
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Use %s() or %s to exit' % (self.name, self.eof)
|
|
||||||
def __call__(self, code=None):
|
|
||||||
# Shells like IDLE catch the SystemExit, but listen when their
|
|
||||||
# stdin wrapper is closed.
|
|
||||||
try:
|
|
||||||
sys.stdin.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
raise SystemExit(code)
|
|
||||||
|
|
||||||
|
|
||||||
class _Printer(object):
|
|
||||||
"""interactive prompt objects for printing the license text, a list of
|
|
||||||
contributors and the copyright notice."""
|
|
||||||
|
|
||||||
MAXLINES = 23
|
|
||||||
|
|
||||||
def __init__(self, name, data, files=(), dirs=()):
|
|
||||||
import os
|
|
||||||
self.__name = name
|
|
||||||
self.__data = data
|
|
||||||
self.__lines = None
|
|
||||||
self.__filenames = [os.path.join(dir, filename)
|
|
||||||
for dir in dirs
|
|
||||||
for filename in files]
|
|
||||||
|
|
||||||
def __setup(self):
|
|
||||||
if self.__lines:
|
|
||||||
return
|
|
||||||
data = None
|
|
||||||
for filename in self.__filenames:
|
|
||||||
try:
|
|
||||||
with open(filename, "r") as fp:
|
|
||||||
data = fp.read()
|
|
||||||
break
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if not data:
|
|
||||||
data = self.__data
|
|
||||||
self.__lines = data.split('\n')
|
|
||||||
self.__linecnt = len(self.__lines)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
self.__setup()
|
|
||||||
if len(self.__lines) <= self.MAXLINES:
|
|
||||||
return "\n".join(self.__lines)
|
|
||||||
else:
|
|
||||||
return "Type %s() to see the full %s text" % ((self.__name,)*2)
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
self.__setup()
|
|
||||||
prompt = 'Hit Return for more, or q (and Return) to quit: '
|
|
||||||
lineno = 0
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
for i in range(lineno, lineno + self.MAXLINES):
|
|
||||||
print(self.__lines[i])
|
|
||||||
except IndexError:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
lineno += self.MAXLINES
|
|
||||||
key = None
|
|
||||||
while key is None:
|
|
||||||
key = input(prompt)
|
|
||||||
if key not in ('', 'q'):
|
|
||||||
key = None
|
|
||||||
if key == 'q':
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class _Helper(object):
|
|
||||||
"""Define the builtin 'help'.
|
|
||||||
|
|
||||||
This is a wrapper around pydoc.help that provides a helpful message
|
|
||||||
when 'help' is typed at the Python interactive prompt.
|
|
||||||
|
|
||||||
Calling help() at the Python prompt starts an interactive help session.
|
|
||||||
Calling help(thing) prints help for the python object 'thing'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "Type help() for interactive help, " \
|
|
||||||
"or help(object) for help about object."
|
|
||||||
def __call__(self, *args, **kwds):
|
|
||||||
import pydoc
|
|
||||||
return pydoc.help(*args, **kwds)
|
|
||||||
1297
Lib/_sre.py
1297
Lib/_sre.py
File diff suppressed because it is too large
Load Diff
@@ -1,249 +0,0 @@
|
|||||||
"""Thread-local objects.
|
|
||||||
|
|
||||||
(Note that this module provides a Python version of the threading.local
|
|
||||||
class. Depending on the version of Python you're using, there may be a
|
|
||||||
faster one available. You should always import the `local` class from
|
|
||||||
`threading`.)
|
|
||||||
|
|
||||||
Thread-local objects support the management of thread-local data.
|
|
||||||
If you have data that you want to be local to a thread, simply create
|
|
||||||
a thread-local object and use its attributes:
|
|
||||||
|
|
||||||
>>> mydata = local()
|
|
||||||
>>> mydata.number = 42
|
|
||||||
>>> mydata.number
|
|
||||||
42
|
|
||||||
|
|
||||||
You can also access the local-object's dictionary:
|
|
||||||
|
|
||||||
>>> mydata.__dict__
|
|
||||||
{'number': 42}
|
|
||||||
>>> mydata.__dict__.setdefault('widgets', [])
|
|
||||||
[]
|
|
||||||
>>> mydata.widgets
|
|
||||||
[]
|
|
||||||
|
|
||||||
What's important about thread-local objects is that their data are
|
|
||||||
local to a thread. If we access the data in a different thread:
|
|
||||||
|
|
||||||
>>> log = []
|
|
||||||
>>> def f():
|
|
||||||
... items = sorted(mydata.__dict__.items())
|
|
||||||
... log.append(items)
|
|
||||||
... mydata.number = 11
|
|
||||||
... log.append(mydata.number)
|
|
||||||
|
|
||||||
>>> import threading
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
>>> log
|
|
||||||
[[], 11]
|
|
||||||
|
|
||||||
we get different data. Furthermore, changes made in the other thread
|
|
||||||
don't affect data seen in this thread:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
42
|
|
||||||
|
|
||||||
Of course, values you get from a local object, including a __dict__
|
|
||||||
attribute, are for whatever thread was current at the time the
|
|
||||||
attribute was read. For that reason, you generally don't want to save
|
|
||||||
these values across threads, as they apply only to the thread they
|
|
||||||
came from.
|
|
||||||
|
|
||||||
You can create custom local objects by subclassing the local class:
|
|
||||||
|
|
||||||
>>> class MyLocal(local):
|
|
||||||
... number = 2
|
|
||||||
... initialized = False
|
|
||||||
... def __init__(self, **kw):
|
|
||||||
... if self.initialized:
|
|
||||||
... raise SystemError('__init__ called too many times')
|
|
||||||
... self.initialized = True
|
|
||||||
... self.__dict__.update(kw)
|
|
||||||
... def squared(self):
|
|
||||||
... return self.number ** 2
|
|
||||||
|
|
||||||
This can be useful to support default values, methods and
|
|
||||||
initialization. Note that if you define an __init__ method, it will be
|
|
||||||
called each time the local object is used in a separate thread. This
|
|
||||||
is necessary to initialize each thread's dictionary.
|
|
||||||
|
|
||||||
Now if we create a local object:
|
|
||||||
|
|
||||||
>>> mydata = MyLocal(color='red')
|
|
||||||
|
|
||||||
Now we have a default number:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
2
|
|
||||||
|
|
||||||
an initial color:
|
|
||||||
|
|
||||||
>>> mydata.color
|
|
||||||
'red'
|
|
||||||
>>> del mydata.color
|
|
||||||
|
|
||||||
And a method that operates on the data:
|
|
||||||
|
|
||||||
>>> mydata.squared()
|
|
||||||
4
|
|
||||||
|
|
||||||
As before, we can access the data in a separate thread:
|
|
||||||
|
|
||||||
>>> log = []
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
>>> log
|
|
||||||
[[('color', 'red'), ('initialized', True)], 11]
|
|
||||||
|
|
||||||
without affecting this thread's data:
|
|
||||||
|
|
||||||
>>> mydata.number
|
|
||||||
2
|
|
||||||
>>> mydata.color
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
AttributeError: 'MyLocal' object has no attribute 'color'
|
|
||||||
|
|
||||||
Note that subclasses can define slots, but they are not thread
|
|
||||||
local. They are shared across threads:
|
|
||||||
|
|
||||||
>>> class MyLocal(local):
|
|
||||||
... __slots__ = 'number'
|
|
||||||
|
|
||||||
>>> mydata = MyLocal()
|
|
||||||
>>> mydata.number = 42
|
|
||||||
>>> mydata.color = 'red'
|
|
||||||
|
|
||||||
So, the separate thread:
|
|
||||||
|
|
||||||
>>> thread = threading.Thread(target=f)
|
|
||||||
>>> thread.start()
|
|
||||||
>>> thread.join()
|
|
||||||
|
|
||||||
affects what we see:
|
|
||||||
|
|
||||||
>>> # TODO: RUSTPYTHON, __slots__
|
|
||||||
>>> mydata.number #doctest: +SKIP
|
|
||||||
11
|
|
||||||
|
|
||||||
>>> del mydata
|
|
||||||
"""
|
|
||||||
|
|
||||||
from weakref import ref
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
__all__ = ["local"]
|
|
||||||
|
|
||||||
# We need to use objects from the threading module, but the threading
|
|
||||||
# module may also want to use our `local` class, if support for locals
|
|
||||||
# isn't compiled in to the `thread` module. This creates potential problems
|
|
||||||
# with circular imports. For that reason, we don't import `threading`
|
|
||||||
# until the bottom of this file (a hack sufficient to worm around the
|
|
||||||
# potential problems). Note that all platforms on CPython do have support
|
|
||||||
# for locals in the `thread` module, and there is no circular import problem
|
|
||||||
# then, so problems introduced by fiddling the order of imports here won't
|
|
||||||
# manifest.
|
|
||||||
|
|
||||||
class _localimpl:
|
|
||||||
"""A class managing thread-local dicts"""
|
|
||||||
__slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# The key used in the Thread objects' attribute dicts.
|
|
||||||
# We keep it a string for speed but make it unlikely to clash with
|
|
||||||
# a "real" attribute.
|
|
||||||
self.key = '_threading_local._localimpl.' + str(id(self))
|
|
||||||
# { id(Thread) -> (ref(Thread), thread-local dict) }
|
|
||||||
self.dicts = {}
|
|
||||||
|
|
||||||
def get_dict(self):
|
|
||||||
"""Return the dict for the current thread. Raises KeyError if none
|
|
||||||
defined."""
|
|
||||||
thread = current_thread()
|
|
||||||
return self.dicts[id(thread)][1]
|
|
||||||
|
|
||||||
def create_dict(self):
|
|
||||||
"""Create a new dict for the current thread, and return it."""
|
|
||||||
localdict = {}
|
|
||||||
key = self.key
|
|
||||||
thread = current_thread()
|
|
||||||
idt = id(thread)
|
|
||||||
def local_deleted(_, key=key):
|
|
||||||
# When the localimpl is deleted, remove the thread attribute.
|
|
||||||
thread = wrthread()
|
|
||||||
if thread is not None:
|
|
||||||
del thread.__dict__[key]
|
|
||||||
def thread_deleted(_, idt=idt):
|
|
||||||
# When the thread is deleted, remove the local dict.
|
|
||||||
# Note that this is suboptimal if the thread object gets
|
|
||||||
# caught in a reference loop. We would like to be called
|
|
||||||
# as soon as the OS-level thread ends instead.
|
|
||||||
local = wrlocal()
|
|
||||||
if local is not None:
|
|
||||||
dct = local.dicts.pop(idt)
|
|
||||||
wrlocal = ref(self, local_deleted)
|
|
||||||
wrthread = ref(thread, thread_deleted)
|
|
||||||
thread.__dict__[key] = wrlocal
|
|
||||||
self.dicts[idt] = wrthread, localdict
|
|
||||||
return localdict
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _patch(self):
|
|
||||||
old = object.__getattribute__(self, '__dict__')
|
|
||||||
impl = object.__getattribute__(self, '_local__impl')
|
|
||||||
try:
|
|
||||||
dct = impl.get_dict()
|
|
||||||
except KeyError:
|
|
||||||
dct = impl.create_dict()
|
|
||||||
args, kw = impl.localargs
|
|
||||||
self.__init__(*args, **kw)
|
|
||||||
with impl.locallock:
|
|
||||||
object.__setattr__(self, '__dict__', dct)
|
|
||||||
yield
|
|
||||||
object.__setattr__(self, '__dict__', old)
|
|
||||||
|
|
||||||
|
|
||||||
class local:
|
|
||||||
__slots__ = '_local__impl', '__dict__'
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kw):
|
|
||||||
if (args or kw) and (cls.__init__ is object.__init__):
|
|
||||||
raise TypeError("Initialization arguments are not supported")
|
|
||||||
self = object.__new__(cls)
|
|
||||||
impl = _localimpl()
|
|
||||||
impl.localargs = (args, kw)
|
|
||||||
impl.locallock = RLock()
|
|
||||||
object.__setattr__(self, '_local__impl', impl)
|
|
||||||
# We need to create the thread dict in anticipation of
|
|
||||||
# __init__ being called, to make sure we don't call it
|
|
||||||
# again ourselves.
|
|
||||||
impl.create_dict()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
with _patch(self):
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
if name == '__dict__':
|
|
||||||
raise AttributeError(
|
|
||||||
"%r object attribute '__dict__' is read-only"
|
|
||||||
% self.__class__.__name__)
|
|
||||||
with _patch(self):
|
|
||||||
return object.__setattr__(self, name, value)
|
|
||||||
|
|
||||||
def __delattr__(self, name):
|
|
||||||
if name == '__dict__':
|
|
||||||
raise AttributeError(
|
|
||||||
"%r object attribute '__dict__' is read-only"
|
|
||||||
% self.__class__.__name__)
|
|
||||||
with _patch(self):
|
|
||||||
return object.__delattr__(self, name)
|
|
||||||
|
|
||||||
|
|
||||||
from threading import current_thread, RLock
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# Access WeakSet through the weakref module.
|
|
||||||
# This code is separated-out because it is needed
|
|
||||||
# by abc.py to load everything else at startup.
|
|
||||||
|
|
||||||
from _weakref import ref
|
|
||||||
|
|
||||||
__all__ = ['WeakSet']
|
|
||||||
|
|
||||||
|
|
||||||
class _IterationGuard:
|
|
||||||
# This context manager registers itself in the current iterators of the
|
|
||||||
# weak container, such as to delay all removals until the context manager
|
|
||||||
# exits.
|
|
||||||
# This technique should be relatively thread-safe (since sets are).
|
|
||||||
|
|
||||||
def __init__(self, weakcontainer):
|
|
||||||
# Don't create cycles
|
|
||||||
self.weakcontainer = ref(weakcontainer)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
w = self.weakcontainer()
|
|
||||||
if w is not None:
|
|
||||||
w._iterating.add(self)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, e, t, b):
|
|
||||||
w = self.weakcontainer()
|
|
||||||
if w is not None:
|
|
||||||
s = w._iterating
|
|
||||||
s.remove(self)
|
|
||||||
if not s:
|
|
||||||
w._commit_removals()
|
|
||||||
|
|
||||||
|
|
||||||
class WeakSet:
|
|
||||||
def __init__(self, data=None):
|
|
||||||
self.data = set()
|
|
||||||
def _remove(item, selfref=ref(self)):
|
|
||||||
self = selfref()
|
|
||||||
if self is not None:
|
|
||||||
if self._iterating:
|
|
||||||
self._pending_removals.append(item)
|
|
||||||
else:
|
|
||||||
self.data.discard(item)
|
|
||||||
self._remove = _remove
|
|
||||||
# A list of keys to be removed
|
|
||||||
self._pending_removals = []
|
|
||||||
self._iterating = set()
|
|
||||||
if data is not None:
|
|
||||||
self.update(data)
|
|
||||||
|
|
||||||
def _commit_removals(self):
|
|
||||||
l = self._pending_removals
|
|
||||||
discard = self.data.discard
|
|
||||||
while l:
|
|
||||||
discard(l.pop())
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
with _IterationGuard(self):
|
|
||||||
for itemref in self.data:
|
|
||||||
item = itemref()
|
|
||||||
if item is not None:
|
|
||||||
# Caveat: the iterator will keep a strong reference to
|
|
||||||
# `item` until it is resumed or closed.
|
|
||||||
yield item
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data) - len(self._pending_removals)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
try:
|
|
||||||
wr = ref(item)
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
return wr in self.data
|
|
||||||
|
|
||||||
def __reduce__(self):
|
|
||||||
return (self.__class__, (list(self),),
|
|
||||||
getattr(self, '__dict__', None))
|
|
||||||
|
|
||||||
def add(self, item):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.add(ref(item, self._remove))
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.clear()
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
return self.__class__(self)
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
itemref = self.data.pop()
|
|
||||||
except KeyError:
|
|
||||||
raise KeyError('pop from empty WeakSet') from None
|
|
||||||
item = itemref()
|
|
||||||
if item is not None:
|
|
||||||
return item
|
|
||||||
|
|
||||||
def remove(self, item):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.remove(ref(item))
|
|
||||||
|
|
||||||
def discard(self, item):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.discard(ref(item))
|
|
||||||
|
|
||||||
def update(self, other):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
for element in other:
|
|
||||||
self.add(element)
|
|
||||||
|
|
||||||
def __ior__(self, other):
|
|
||||||
self.update(other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def difference(self, other):
|
|
||||||
newset = self.copy()
|
|
||||||
newset.difference_update(other)
|
|
||||||
return newset
|
|
||||||
__sub__ = difference
|
|
||||||
|
|
||||||
def difference_update(self, other):
|
|
||||||
self.__isub__(other)
|
|
||||||
def __isub__(self, other):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
if self is other:
|
|
||||||
self.data.clear()
|
|
||||||
else:
|
|
||||||
self.data.difference_update(ref(item) for item in other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def intersection(self, other):
|
|
||||||
return self.__class__(item for item in other if item in self)
|
|
||||||
__and__ = intersection
|
|
||||||
|
|
||||||
def intersection_update(self, other):
|
|
||||||
self.__iand__(other)
|
|
||||||
def __iand__(self, other):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
self.data.intersection_update(ref(item) for item in other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def issubset(self, other):
|
|
||||||
return self.data.issubset(ref(item) for item in other)
|
|
||||||
__le__ = issubset
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return self.data < set(map(ref, other))
|
|
||||||
|
|
||||||
def issuperset(self, other):
|
|
||||||
return self.data.issuperset(ref(item) for item in other)
|
|
||||||
__ge__ = issuperset
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.data > set(map(ref, other))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, self.__class__):
|
|
||||||
return NotImplemented
|
|
||||||
return self.data == set(map(ref, other))
|
|
||||||
|
|
||||||
def symmetric_difference(self, other):
|
|
||||||
newset = self.copy()
|
|
||||||
newset.symmetric_difference_update(other)
|
|
||||||
return newset
|
|
||||||
__xor__ = symmetric_difference
|
|
||||||
|
|
||||||
def symmetric_difference_update(self, other):
|
|
||||||
self.__ixor__(other)
|
|
||||||
def __ixor__(self, other):
|
|
||||||
if self._pending_removals:
|
|
||||||
self._commit_removals()
|
|
||||||
if self is other:
|
|
||||||
self.data.clear()
|
|
||||||
else:
|
|
||||||
self.data.symmetric_difference_update(ref(item, self._remove) for item in other)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def union(self, other):
|
|
||||||
return self.__class__(e for s in (self, other) for e in s)
|
|
||||||
__or__ = union
|
|
||||||
|
|
||||||
def isdisjoint(self, other):
|
|
||||||
return len(self.intersection(other)) == 0
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self.data)
|
|
||||||
170
Lib/abc.py
170
Lib/abc.py
@@ -1,170 +0,0 @@
|
|||||||
# Copyright 2007 Google, Inc. All Rights Reserved.
|
|
||||||
# Licensed to PSF under a Contributor Agreement.
|
|
||||||
|
|
||||||
"""Abstract Base Classes (ABCs) according to PEP 3119."""
|
|
||||||
|
|
||||||
|
|
||||||
def abstractmethod(funcobj):
|
|
||||||
"""A decorator indicating abstract methods.
|
|
||||||
|
|
||||||
Requires that the metaclass is ABCMeta or derived from it. A
|
|
||||||
class that has a metaclass derived from ABCMeta cannot be
|
|
||||||
instantiated unless all of its abstract methods are overridden.
|
|
||||||
The abstract methods can be called using any of the normal
|
|
||||||
'super' call mechanisms.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
|
||||||
@abstractmethod
|
|
||||||
def my_abstract_method(self, ...):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
funcobj.__isabstractmethod__ = True
|
|
||||||
return funcobj
|
|
||||||
|
|
||||||
|
|
||||||
class abstractclassmethod(classmethod):
|
|
||||||
"""A decorator indicating abstract classmethods.
|
|
||||||
|
|
||||||
Similar to abstractmethod.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
|
||||||
@abstractclassmethod
|
|
||||||
def my_abstract_classmethod(cls, ...):
|
|
||||||
...
|
|
||||||
|
|
||||||
'abstractclassmethod' is deprecated. Use 'classmethod' with
|
|
||||||
'abstractmethod' instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__isabstractmethod__ = True
|
|
||||||
|
|
||||||
def __init__(self, callable):
|
|
||||||
callable.__isabstractmethod__ = True
|
|
||||||
super().__init__(callable)
|
|
||||||
|
|
||||||
|
|
||||||
class abstractstaticmethod(staticmethod):
|
|
||||||
"""A decorator indicating abstract staticmethods.
|
|
||||||
|
|
||||||
Similar to abstractmethod.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
|
||||||
@abstractstaticmethod
|
|
||||||
def my_abstract_staticmethod(...):
|
|
||||||
...
|
|
||||||
|
|
||||||
'abstractstaticmethod' is deprecated. Use 'staticmethod' with
|
|
||||||
'abstractmethod' instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__isabstractmethod__ = True
|
|
||||||
|
|
||||||
def __init__(self, callable):
|
|
||||||
callable.__isabstractmethod__ = True
|
|
||||||
super().__init__(callable)
|
|
||||||
|
|
||||||
|
|
||||||
class abstractproperty(property):
|
|
||||||
"""A decorator indicating abstract properties.
|
|
||||||
|
|
||||||
Requires that the metaclass is ABCMeta or derived from it. A
|
|
||||||
class that has a metaclass derived from ABCMeta cannot be
|
|
||||||
instantiated unless all of its abstract properties are overridden.
|
|
||||||
The abstract properties can be called using any of the normal
|
|
||||||
'super' call mechanisms.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
|
||||||
@abstractproperty
|
|
||||||
def my_abstract_property(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
This defines a read-only property; you can also define a read-write
|
|
||||||
abstract property using the 'long' form of property declaration:
|
|
||||||
|
|
||||||
class C(metaclass=ABCMeta):
|
|
||||||
def getx(self): ...
|
|
||||||
def setx(self, value): ...
|
|
||||||
x = abstractproperty(getx, setx)
|
|
||||||
|
|
||||||
'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__isabstractmethod__ = True
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from _abc import (get_cache_token, _abc_init, _abc_register,
|
|
||||||
_abc_instancecheck, _abc_subclasscheck, _get_dump,
|
|
||||||
_reset_registry, _reset_caches)
|
|
||||||
except ImportError:
|
|
||||||
from _py_abc import ABCMeta, get_cache_token
|
|
||||||
ABCMeta.__module__ = 'abc'
|
|
||||||
else:
|
|
||||||
class ABCMeta(type):
|
|
||||||
"""Metaclass for defining Abstract Base Classes (ABCs).
|
|
||||||
|
|
||||||
Use this metaclass to create an ABC. An ABC can be subclassed
|
|
||||||
directly, and then acts as a mix-in class. You can also register
|
|
||||||
unrelated concrete classes (even built-in classes) and unrelated
|
|
||||||
ABCs as 'virtual subclasses' -- these and their descendants will
|
|
||||||
be considered subclasses of the registering ABC by the built-in
|
|
||||||
issubclass() function, but the registering ABC won't show up in
|
|
||||||
their MRO (Method Resolution Order) nor will method
|
|
||||||
implementations defined by the registering ABC be callable (not
|
|
||||||
even via super()).
|
|
||||||
"""
|
|
||||||
def __new__(mcls, name, bases, namespace, **kwargs):
|
|
||||||
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
|
|
||||||
_abc_init(cls)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
def register(cls, subclass):
|
|
||||||
"""Register a virtual subclass of an ABC.
|
|
||||||
|
|
||||||
Returns the subclass, to allow usage as a class decorator.
|
|
||||||
"""
|
|
||||||
return _abc_register(cls, subclass)
|
|
||||||
|
|
||||||
def __instancecheck__(cls, instance):
|
|
||||||
"""Override for isinstance(instance, cls)."""
|
|
||||||
return _abc_instancecheck(cls, instance)
|
|
||||||
|
|
||||||
def __subclasscheck__(cls, subclass):
|
|
||||||
"""Override for issubclass(subclass, cls)."""
|
|
||||||
return _abc_subclasscheck(cls, subclass)
|
|
||||||
|
|
||||||
def _dump_registry(cls, file=None):
|
|
||||||
"""Debug helper to print the ABC registry."""
|
|
||||||
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
|
|
||||||
print(f"Inv. counter: {get_cache_token()}", file=file)
|
|
||||||
(_abc_registry, _abc_cache, _abc_negative_cache,
|
|
||||||
_abc_negative_cache_version) = _get_dump(cls)
|
|
||||||
print(f"_abc_registry: {_abc_registry!r}", file=file)
|
|
||||||
print(f"_abc_cache: {_abc_cache!r}", file=file)
|
|
||||||
print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file)
|
|
||||||
print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}",
|
|
||||||
file=file)
|
|
||||||
|
|
||||||
def _abc_registry_clear(cls):
|
|
||||||
"""Clear the registry (for debugging or testing)."""
|
|
||||||
_reset_registry(cls)
|
|
||||||
|
|
||||||
def _abc_caches_clear(cls):
|
|
||||||
"""Clear the caches (for debugging or testing)."""
|
|
||||||
_reset_caches(cls)
|
|
||||||
|
|
||||||
|
|
||||||
class ABC(metaclass=ABCMeta):
|
|
||||||
"""Helper class that provides a standard way to create an ABC using
|
|
||||||
inheritance.
|
|
||||||
"""
|
|
||||||
__slots__ = ()
|
|
||||||
951
Lib/aifc.py
951
Lib/aifc.py
@@ -1,951 +0,0 @@
|
|||||||
"""Stuff to parse AIFF-C and AIFF files.
|
|
||||||
|
|
||||||
Unless explicitly stated otherwise, the description below is true
|
|
||||||
both for AIFF-C files and AIFF files.
|
|
||||||
|
|
||||||
An AIFF-C file has the following structure.
|
|
||||||
|
|
||||||
+-----------------+
|
|
||||||
| FORM |
|
|
||||||
+-----------------+
|
|
||||||
| <size> |
|
|
||||||
+----+------------+
|
|
||||||
| | AIFC |
|
|
||||||
| +------------+
|
|
||||||
| | <chunks> |
|
|
||||||
| | . |
|
|
||||||
| | . |
|
|
||||||
| | . |
|
|
||||||
+----+------------+
|
|
||||||
|
|
||||||
An AIFF file has the string "AIFF" instead of "AIFC".
|
|
||||||
|
|
||||||
A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
|
|
||||||
big endian order), followed by the data. The size field does not include
|
|
||||||
the size of the 8 byte header.
|
|
||||||
|
|
||||||
The following chunk types are recognized.
|
|
||||||
|
|
||||||
FVER
|
|
||||||
<version number of AIFF-C defining document> (AIFF-C only).
|
|
||||||
MARK
|
|
||||||
<# of markers> (2 bytes)
|
|
||||||
list of markers:
|
|
||||||
<marker ID> (2 bytes, must be > 0)
|
|
||||||
<position> (4 bytes)
|
|
||||||
<marker name> ("pstring")
|
|
||||||
COMM
|
|
||||||
<# of channels> (2 bytes)
|
|
||||||
<# of sound frames> (4 bytes)
|
|
||||||
<size of the samples> (2 bytes)
|
|
||||||
<sampling frequency> (10 bytes, IEEE 80-bit extended
|
|
||||||
floating point)
|
|
||||||
in AIFF-C files only:
|
|
||||||
<compression type> (4 bytes)
|
|
||||||
<human-readable version of compression type> ("pstring")
|
|
||||||
SSND
|
|
||||||
<offset> (4 bytes, not used by this program)
|
|
||||||
<blocksize> (4 bytes, not used by this program)
|
|
||||||
<sound data>
|
|
||||||
|
|
||||||
A pstring consists of 1 byte length, a string of characters, and 0 or 1
|
|
||||||
byte pad to make the total length even.
|
|
||||||
|
|
||||||
Usage.
|
|
||||||
|
|
||||||
Reading AIFF files:
|
|
||||||
f = aifc.open(file, 'r')
|
|
||||||
where file is either the name of a file or an open file pointer.
|
|
||||||
The open file pointer must have methods read(), seek(), and close().
|
|
||||||
In some types of audio files, if the setpos() method is not used,
|
|
||||||
the seek() method is not necessary.
|
|
||||||
|
|
||||||
This returns an instance of a class with the following public methods:
|
|
||||||
getnchannels() -- returns number of audio channels (1 for
|
|
||||||
mono, 2 for stereo)
|
|
||||||
getsampwidth() -- returns sample width in bytes
|
|
||||||
getframerate() -- returns sampling frequency
|
|
||||||
getnframes() -- returns number of audio frames
|
|
||||||
getcomptype() -- returns compression type ('NONE' for AIFF files)
|
|
||||||
getcompname() -- returns human-readable version of
|
|
||||||
compression type ('not compressed' for AIFF files)
|
|
||||||
getparams() -- returns a namedtuple consisting of all of the
|
|
||||||
above in the above order
|
|
||||||
getmarkers() -- get the list of marks in the audio file or None
|
|
||||||
if there are no marks
|
|
||||||
getmark(id) -- get mark with the specified id (raises an error
|
|
||||||
if the mark does not exist)
|
|
||||||
readframes(n) -- returns at most n frames of audio
|
|
||||||
rewind() -- rewind to the beginning of the audio stream
|
|
||||||
setpos(pos) -- seek to the specified position
|
|
||||||
tell() -- return the current position
|
|
||||||
close() -- close the instance (make it unusable)
|
|
||||||
The position returned by tell(), the position given to setpos() and
|
|
||||||
the position of marks are all compatible and have nothing to do with
|
|
||||||
the actual position in the file.
|
|
||||||
The close() method is called automatically when the class instance
|
|
||||||
is destroyed.
|
|
||||||
|
|
||||||
Writing AIFF files:
|
|
||||||
f = aifc.open(file, 'w')
|
|
||||||
where file is either the name of a file or an open file pointer.
|
|
||||||
The open file pointer must have methods write(), tell(), seek(), and
|
|
||||||
close().
|
|
||||||
|
|
||||||
This returns an instance of a class with the following public methods:
|
|
||||||
aiff() -- create an AIFF file (AIFF-C default)
|
|
||||||
aifc() -- create an AIFF-C file
|
|
||||||
setnchannels(n) -- set the number of channels
|
|
||||||
setsampwidth(n) -- set the sample width
|
|
||||||
setframerate(n) -- set the frame rate
|
|
||||||
setnframes(n) -- set the number of frames
|
|
||||||
setcomptype(type, name)
|
|
||||||
-- set the compression type and the
|
|
||||||
human-readable compression type
|
|
||||||
setparams(tuple)
|
|
||||||
-- set all parameters at once
|
|
||||||
setmark(id, pos, name)
|
|
||||||
-- add specified mark to the list of marks
|
|
||||||
tell() -- return current position in output file (useful
|
|
||||||
in combination with setmark())
|
|
||||||
writeframesraw(data)
|
|
||||||
-- write audio frames without pathing up the
|
|
||||||
file header
|
|
||||||
writeframes(data)
|
|
||||||
-- write audio frames and patch up the file header
|
|
||||||
close() -- patch up the file header and close the
|
|
||||||
output file
|
|
||||||
You should set the parameters before the first writeframesraw or
|
|
||||||
writeframes. The total number of frames does not need to be set,
|
|
||||||
but when it is set to the correct value, the header does not have to
|
|
||||||
be patched up.
|
|
||||||
It is best to first set all parameters, perhaps possibly the
|
|
||||||
compression type, and then write audio frames using writeframesraw.
|
|
||||||
When all frames have been written, either call writeframes(b'') or
|
|
||||||
close() to patch up the sizes in the header.
|
|
||||||
Marks can be added anytime. If there are any marks, you must call
|
|
||||||
close() after all frames have been written.
|
|
||||||
The close() method is called automatically when the class instance
|
|
||||||
is destroyed.
|
|
||||||
|
|
||||||
When a file is opened with the extension '.aiff', an AIFF file is
|
|
||||||
written, otherwise an AIFF-C file is written. This default can be
|
|
||||||
changed by calling aiff() or aifc() before the first writeframes or
|
|
||||||
writeframesraw.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import struct
|
|
||||||
import builtins
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
__all__ = ["Error", "open", "openfp"]
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
_AIFC_version = 0xA2805140 # Version 1 of AIFF-C
|
|
||||||
|
|
||||||
def _read_long(file):
|
|
||||||
try:
|
|
||||||
return struct.unpack('>l', file.read(4))[0]
|
|
||||||
except struct.error:
|
|
||||||
raise EOFError from None
|
|
||||||
|
|
||||||
def _read_ulong(file):
|
|
||||||
try:
|
|
||||||
return struct.unpack('>L', file.read(4))[0]
|
|
||||||
except struct.error:
|
|
||||||
raise EOFError from None
|
|
||||||
|
|
||||||
def _read_short(file):
|
|
||||||
try:
|
|
||||||
return struct.unpack('>h', file.read(2))[0]
|
|
||||||
except struct.error:
|
|
||||||
raise EOFError from None
|
|
||||||
|
|
||||||
def _read_ushort(file):
|
|
||||||
try:
|
|
||||||
return struct.unpack('>H', file.read(2))[0]
|
|
||||||
except struct.error:
|
|
||||||
raise EOFError from None
|
|
||||||
|
|
||||||
def _read_string(file):
|
|
||||||
length = ord(file.read(1))
|
|
||||||
if length == 0:
|
|
||||||
data = b''
|
|
||||||
else:
|
|
||||||
data = file.read(length)
|
|
||||||
if length & 1 == 0:
|
|
||||||
dummy = file.read(1)
|
|
||||||
return data
|
|
||||||
|
|
||||||
_HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
|
|
||||||
|
|
||||||
def _read_float(f): # 10 bytes
|
|
||||||
expon = _read_short(f) # 2 bytes
|
|
||||||
sign = 1
|
|
||||||
if expon < 0:
|
|
||||||
sign = -1
|
|
||||||
expon = expon + 0x8000
|
|
||||||
himant = _read_ulong(f) # 4 bytes
|
|
||||||
lomant = _read_ulong(f) # 4 bytes
|
|
||||||
if expon == himant == lomant == 0:
|
|
||||||
f = 0.0
|
|
||||||
elif expon == 0x7FFF:
|
|
||||||
f = _HUGE_VAL
|
|
||||||
else:
|
|
||||||
expon = expon - 16383
|
|
||||||
f = (himant * 0x100000000 + lomant) * pow(2.0, expon - 63)
|
|
||||||
return sign * f
|
|
||||||
|
|
||||||
def _write_short(f, x):
|
|
||||||
f.write(struct.pack('>h', x))
|
|
||||||
|
|
||||||
def _write_ushort(f, x):
|
|
||||||
f.write(struct.pack('>H', x))
|
|
||||||
|
|
||||||
def _write_long(f, x):
|
|
||||||
f.write(struct.pack('>l', x))
|
|
||||||
|
|
||||||
def _write_ulong(f, x):
|
|
||||||
f.write(struct.pack('>L', x))
|
|
||||||
|
|
||||||
def _write_string(f, s):
|
|
||||||
if len(s) > 255:
|
|
||||||
raise ValueError("string exceeds maximum pstring length")
|
|
||||||
f.write(struct.pack('B', len(s)))
|
|
||||||
f.write(s)
|
|
||||||
if len(s) & 1 == 0:
|
|
||||||
f.write(b'\x00')
|
|
||||||
|
|
||||||
def _write_float(f, x):
|
|
||||||
import math
|
|
||||||
if x < 0:
|
|
||||||
sign = 0x8000
|
|
||||||
x = x * -1
|
|
||||||
else:
|
|
||||||
sign = 0
|
|
||||||
if x == 0:
|
|
||||||
expon = 0
|
|
||||||
himant = 0
|
|
||||||
lomant = 0
|
|
||||||
else:
|
|
||||||
fmant, expon = math.frexp(x)
|
|
||||||
if expon > 16384 or fmant >= 1 or fmant != fmant: # Infinity or NaN
|
|
||||||
expon = sign|0x7FFF
|
|
||||||
himant = 0
|
|
||||||
lomant = 0
|
|
||||||
else: # Finite
|
|
||||||
expon = expon + 16382
|
|
||||||
if expon < 0: # denormalized
|
|
||||||
fmant = math.ldexp(fmant, expon)
|
|
||||||
expon = 0
|
|
||||||
expon = expon | sign
|
|
||||||
fmant = math.ldexp(fmant, 32)
|
|
||||||
fsmant = math.floor(fmant)
|
|
||||||
himant = int(fsmant)
|
|
||||||
fmant = math.ldexp(fmant - fsmant, 32)
|
|
||||||
fsmant = math.floor(fmant)
|
|
||||||
lomant = int(fsmant)
|
|
||||||
_write_ushort(f, expon)
|
|
||||||
_write_ulong(f, himant)
|
|
||||||
_write_ulong(f, lomant)
|
|
||||||
|
|
||||||
from chunk import Chunk
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
_aifc_params = namedtuple('_aifc_params',
|
|
||||||
'nchannels sampwidth framerate nframes comptype compname')
|
|
||||||
|
|
||||||
_aifc_params.nchannels.__doc__ = 'Number of audio channels (1 for mono, 2 for stereo)'
|
|
||||||
_aifc_params.sampwidth.__doc__ = 'Sample width in bytes'
|
|
||||||
_aifc_params.framerate.__doc__ = 'Sampling frequency'
|
|
||||||
_aifc_params.nframes.__doc__ = 'Number of audio frames'
|
|
||||||
_aifc_params.comptype.__doc__ = 'Compression type ("NONE" for AIFF files)'
|
|
||||||
_aifc_params.compname.__doc__ = ("""\
|
|
||||||
A human-readable version of the compression type
|
|
||||||
('not compressed' for AIFF files)""")
|
|
||||||
|
|
||||||
|
|
||||||
class Aifc_read:
|
|
||||||
# Variables used in this class:
|
|
||||||
#
|
|
||||||
# These variables are available to the user though appropriate
|
|
||||||
# methods of this class:
|
|
||||||
# _file -- the open file with methods read(), close(), and seek()
|
|
||||||
# set through the __init__() method
|
|
||||||
# _nchannels -- the number of audio channels
|
|
||||||
# available through the getnchannels() method
|
|
||||||
# _nframes -- the number of audio frames
|
|
||||||
# available through the getnframes() method
|
|
||||||
# _sampwidth -- the number of bytes per audio sample
|
|
||||||
# available through the getsampwidth() method
|
|
||||||
# _framerate -- the sampling frequency
|
|
||||||
# available through the getframerate() method
|
|
||||||
# _comptype -- the AIFF-C compression type ('NONE' if AIFF)
|
|
||||||
# available through the getcomptype() method
|
|
||||||
# _compname -- the human-readable AIFF-C compression type
|
|
||||||
# available through the getcomptype() method
|
|
||||||
# _markers -- the marks in the audio file
|
|
||||||
# available through the getmarkers() and getmark()
|
|
||||||
# methods
|
|
||||||
# _soundpos -- the position in the audio stream
|
|
||||||
# available through the tell() method, set through the
|
|
||||||
# setpos() method
|
|
||||||
#
|
|
||||||
# These variables are used internally only:
|
|
||||||
# _version -- the AIFF-C version number
|
|
||||||
# _decomp -- the decompressor from builtin module cl
|
|
||||||
# _comm_chunk_read -- 1 iff the COMM chunk has been read
|
|
||||||
# _aifc -- 1 iff reading an AIFF-C file
|
|
||||||
# _ssnd_seek_needed -- 1 iff positioned correctly in audio
|
|
||||||
# file for readframes()
|
|
||||||
# _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
|
|
||||||
# _framesize -- size of one frame in the file
|
|
||||||
|
|
||||||
_file = None # Set here since __del__ checks it
|
|
||||||
|
|
||||||
def initfp(self, file):
|
|
||||||
self._version = 0
|
|
||||||
self._convert = None
|
|
||||||
self._markers = []
|
|
||||||
self._soundpos = 0
|
|
||||||
self._file = file
|
|
||||||
chunk = Chunk(file)
|
|
||||||
if chunk.getname() != b'FORM':
|
|
||||||
raise Error('file does not start with FORM id')
|
|
||||||
formdata = chunk.read(4)
|
|
||||||
if formdata == b'AIFF':
|
|
||||||
self._aifc = 0
|
|
||||||
elif formdata == b'AIFC':
|
|
||||||
self._aifc = 1
|
|
||||||
else:
|
|
||||||
raise Error('not an AIFF or AIFF-C file')
|
|
||||||
self._comm_chunk_read = 0
|
|
||||||
self._ssnd_chunk = None
|
|
||||||
while 1:
|
|
||||||
self._ssnd_seek_needed = 1
|
|
||||||
try:
|
|
||||||
chunk = Chunk(self._file)
|
|
||||||
except EOFError:
|
|
||||||
break
|
|
||||||
chunkname = chunk.getname()
|
|
||||||
if chunkname == b'COMM':
|
|
||||||
self._read_comm_chunk(chunk)
|
|
||||||
self._comm_chunk_read = 1
|
|
||||||
elif chunkname == b'SSND':
|
|
||||||
self._ssnd_chunk = chunk
|
|
||||||
dummy = chunk.read(8)
|
|
||||||
self._ssnd_seek_needed = 0
|
|
||||||
elif chunkname == b'FVER':
|
|
||||||
self._version = _read_ulong(chunk)
|
|
||||||
elif chunkname == b'MARK':
|
|
||||||
self._readmark(chunk)
|
|
||||||
chunk.skip()
|
|
||||||
if not self._comm_chunk_read or not self._ssnd_chunk:
|
|
||||||
raise Error('COMM chunk and/or SSND chunk missing')
|
|
||||||
|
|
||||||
def __init__(self, f):
|
|
||||||
if isinstance(f, str):
|
|
||||||
file_object = builtins.open(f, 'rb')
|
|
||||||
try:
|
|
||||||
self.initfp(file_object)
|
|
||||||
except:
|
|
||||||
file_object.close()
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# assume it is an open file object already
|
|
||||||
self.initfp(f)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
#
|
|
||||||
# User visible methods.
|
|
||||||
#
|
|
||||||
def getfp(self):
|
|
||||||
return self._file
|
|
||||||
|
|
||||||
def rewind(self):
|
|
||||||
self._ssnd_seek_needed = 1
|
|
||||||
self._soundpos = 0
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
file = self._file
|
|
||||||
if file is not None:
|
|
||||||
self._file = None
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
return self._soundpos
|
|
||||||
|
|
||||||
def getnchannels(self):
|
|
||||||
return self._nchannels
|
|
||||||
|
|
||||||
def getnframes(self):
|
|
||||||
return self._nframes
|
|
||||||
|
|
||||||
def getsampwidth(self):
|
|
||||||
return self._sampwidth
|
|
||||||
|
|
||||||
def getframerate(self):
|
|
||||||
return self._framerate
|
|
||||||
|
|
||||||
def getcomptype(self):
|
|
||||||
return self._comptype
|
|
||||||
|
|
||||||
def getcompname(self):
|
|
||||||
return self._compname
|
|
||||||
|
|
||||||
## def getversion(self):
|
|
||||||
## return self._version
|
|
||||||
|
|
||||||
def getparams(self):
|
|
||||||
return _aifc_params(self.getnchannels(), self.getsampwidth(),
|
|
||||||
self.getframerate(), self.getnframes(),
|
|
||||||
self.getcomptype(), self.getcompname())
|
|
||||||
|
|
||||||
def getmarkers(self):
|
|
||||||
if len(self._markers) == 0:
|
|
||||||
return None
|
|
||||||
return self._markers
|
|
||||||
|
|
||||||
def getmark(self, id):
|
|
||||||
for marker in self._markers:
|
|
||||||
if id == marker[0]:
|
|
||||||
return marker
|
|
||||||
raise Error('marker {0!r} does not exist'.format(id))
|
|
||||||
|
|
||||||
def setpos(self, pos):
|
|
||||||
if pos < 0 or pos > self._nframes:
|
|
||||||
raise Error('position not in range')
|
|
||||||
self._soundpos = pos
|
|
||||||
self._ssnd_seek_needed = 1
|
|
||||||
|
|
||||||
def readframes(self, nframes):
|
|
||||||
if self._ssnd_seek_needed:
|
|
||||||
self._ssnd_chunk.seek(0)
|
|
||||||
dummy = self._ssnd_chunk.read(8)
|
|
||||||
pos = self._soundpos * self._framesize
|
|
||||||
if pos:
|
|
||||||
self._ssnd_chunk.seek(pos + 8)
|
|
||||||
self._ssnd_seek_needed = 0
|
|
||||||
if nframes == 0:
|
|
||||||
return b''
|
|
||||||
data = self._ssnd_chunk.read(nframes * self._framesize)
|
|
||||||
if self._convert and data:
|
|
||||||
data = self._convert(data)
|
|
||||||
self._soundpos = self._soundpos + len(data) // (self._nchannels
|
|
||||||
* self._sampwidth)
|
|
||||||
return data
|
|
||||||
|
|
||||||
#
|
|
||||||
# Internal methods.
|
|
||||||
#
|
|
||||||
|
|
||||||
def _alaw2lin(self, data):
|
|
||||||
import audioop
|
|
||||||
return audioop.alaw2lin(data, 2)
|
|
||||||
|
|
||||||
def _ulaw2lin(self, data):
|
|
||||||
import audioop
|
|
||||||
return audioop.ulaw2lin(data, 2)
|
|
||||||
|
|
||||||
def _adpcm2lin(self, data):
|
|
||||||
import audioop
|
|
||||||
if not hasattr(self, '_adpcmstate'):
|
|
||||||
# first time
|
|
||||||
self._adpcmstate = None
|
|
||||||
data, self._adpcmstate = audioop.adpcm2lin(data, 2, self._adpcmstate)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _read_comm_chunk(self, chunk):
|
|
||||||
self._nchannels = _read_short(chunk)
|
|
||||||
self._nframes = _read_long(chunk)
|
|
||||||
self._sampwidth = (_read_short(chunk) + 7) // 8
|
|
||||||
self._framerate = int(_read_float(chunk))
|
|
||||||
if self._sampwidth <= 0:
|
|
||||||
raise Error('bad sample width')
|
|
||||||
if self._nchannels <= 0:
|
|
||||||
raise Error('bad # of channels')
|
|
||||||
self._framesize = self._nchannels * self._sampwidth
|
|
||||||
if self._aifc:
|
|
||||||
#DEBUG: SGI's soundeditor produces a bad size :-(
|
|
||||||
kludge = 0
|
|
||||||
if chunk.chunksize == 18:
|
|
||||||
kludge = 1
|
|
||||||
warnings.warn('Warning: bad COMM chunk size')
|
|
||||||
chunk.chunksize = 23
|
|
||||||
#DEBUG end
|
|
||||||
self._comptype = chunk.read(4)
|
|
||||||
#DEBUG start
|
|
||||||
if kludge:
|
|
||||||
length = ord(chunk.file.read(1))
|
|
||||||
if length & 1 == 0:
|
|
||||||
length = length + 1
|
|
||||||
chunk.chunksize = chunk.chunksize + length
|
|
||||||
chunk.file.seek(-1, 1)
|
|
||||||
#DEBUG end
|
|
||||||
self._compname = _read_string(chunk)
|
|
||||||
if self._comptype != b'NONE':
|
|
||||||
if self._comptype == b'G722':
|
|
||||||
self._convert = self._adpcm2lin
|
|
||||||
elif self._comptype in (b'ulaw', b'ULAW'):
|
|
||||||
self._convert = self._ulaw2lin
|
|
||||||
elif self._comptype in (b'alaw', b'ALAW'):
|
|
||||||
self._convert = self._alaw2lin
|
|
||||||
else:
|
|
||||||
raise Error('unsupported compression type')
|
|
||||||
self._sampwidth = 2
|
|
||||||
else:
|
|
||||||
self._comptype = b'NONE'
|
|
||||||
self._compname = b'not compressed'
|
|
||||||
|
|
||||||
def _readmark(self, chunk):
|
|
||||||
nmarkers = _read_short(chunk)
|
|
||||||
# Some files appear to contain invalid counts.
|
|
||||||
# Cope with this by testing for EOF.
|
|
||||||
try:
|
|
||||||
for i in range(nmarkers):
|
|
||||||
id = _read_short(chunk)
|
|
||||||
pos = _read_long(chunk)
|
|
||||||
name = _read_string(chunk)
|
|
||||||
if pos or name:
|
|
||||||
# some files appear to have
|
|
||||||
# dummy markers consisting of
|
|
||||||
# a position 0 and name ''
|
|
||||||
self._markers.append((id, pos, name))
|
|
||||||
except EOFError:
|
|
||||||
w = ('Warning: MARK chunk contains only %s marker%s instead of %s' %
|
|
||||||
(len(self._markers), '' if len(self._markers) == 1 else 's',
|
|
||||||
nmarkers))
|
|
||||||
warnings.warn(w)
|
|
||||||
|
|
||||||
class Aifc_write:
|
|
||||||
# Variables used in this class:
|
|
||||||
#
|
|
||||||
# These variables are user settable through appropriate methods
|
|
||||||
# of this class:
|
|
||||||
# _file -- the open file with methods write(), close(), tell(), seek()
|
|
||||||
# set through the __init__() method
|
|
||||||
# _comptype -- the AIFF-C compression type ('NONE' in AIFF)
|
|
||||||
# set through the setcomptype() or setparams() method
|
|
||||||
# _compname -- the human-readable AIFF-C compression type
|
|
||||||
# set through the setcomptype() or setparams() method
|
|
||||||
# _nchannels -- the number of audio channels
|
|
||||||
# set through the setnchannels() or setparams() method
|
|
||||||
# _sampwidth -- the number of bytes per audio sample
|
|
||||||
# set through the setsampwidth() or setparams() method
|
|
||||||
# _framerate -- the sampling frequency
|
|
||||||
# set through the setframerate() or setparams() method
|
|
||||||
# _nframes -- the number of audio frames written to the header
|
|
||||||
# set through the setnframes() or setparams() method
|
|
||||||
# _aifc -- whether we're writing an AIFF-C file or an AIFF file
|
|
||||||
# set through the aifc() method, reset through the
|
|
||||||
# aiff() method
|
|
||||||
#
|
|
||||||
# These variables are used internally only:
|
|
||||||
# _version -- the AIFF-C version number
|
|
||||||
# _comp -- the compressor from builtin module cl
|
|
||||||
# _nframeswritten -- the number of audio frames actually written
|
|
||||||
# _datalength -- the size of the audio samples written to the header
|
|
||||||
# _datawritten -- the size of the audio samples actually written
|
|
||||||
|
|
||||||
_file = None # Set here since __del__ checks it
|
|
||||||
|
|
||||||
def __init__(self, f):
|
|
||||||
if isinstance(f, str):
|
|
||||||
file_object = builtins.open(f, 'wb')
|
|
||||||
try:
|
|
||||||
self.initfp(file_object)
|
|
||||||
except:
|
|
||||||
file_object.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
# treat .aiff file extensions as non-compressed audio
|
|
||||||
if f.endswith('.aiff'):
|
|
||||||
self._aifc = 0
|
|
||||||
else:
|
|
||||||
# assume it is an open file object already
|
|
||||||
self.initfp(f)
|
|
||||||
|
|
||||||
def initfp(self, file):
|
|
||||||
self._file = file
|
|
||||||
self._version = _AIFC_version
|
|
||||||
self._comptype = b'NONE'
|
|
||||||
self._compname = b'not compressed'
|
|
||||||
self._convert = None
|
|
||||||
self._nchannels = 0
|
|
||||||
self._sampwidth = 0
|
|
||||||
self._framerate = 0
|
|
||||||
self._nframes = 0
|
|
||||||
self._nframeswritten = 0
|
|
||||||
self._datawritten = 0
|
|
||||||
self._datalength = 0
|
|
||||||
self._markers = []
|
|
||||||
self._marklength = 0
|
|
||||||
self._aifc = 1 # AIFF-C is default
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
#
|
|
||||||
# User visible methods.
|
|
||||||
#
|
|
||||||
def aiff(self):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
self._aifc = 0
|
|
||||||
|
|
||||||
def aifc(self):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
self._aifc = 1
|
|
||||||
|
|
||||||
def setnchannels(self, nchannels):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
if nchannels < 1:
|
|
||||||
raise Error('bad # of channels')
|
|
||||||
self._nchannels = nchannels
|
|
||||||
|
|
||||||
def getnchannels(self):
|
|
||||||
if not self._nchannels:
|
|
||||||
raise Error('number of channels not set')
|
|
||||||
return self._nchannels
|
|
||||||
|
|
||||||
def setsampwidth(self, sampwidth):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
if sampwidth < 1 or sampwidth > 4:
|
|
||||||
raise Error('bad sample width')
|
|
||||||
self._sampwidth = sampwidth
|
|
||||||
|
|
||||||
def getsampwidth(self):
|
|
||||||
if not self._sampwidth:
|
|
||||||
raise Error('sample width not set')
|
|
||||||
return self._sampwidth
|
|
||||||
|
|
||||||
def setframerate(self, framerate):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
if framerate <= 0:
|
|
||||||
raise Error('bad frame rate')
|
|
||||||
self._framerate = framerate
|
|
||||||
|
|
||||||
def getframerate(self):
|
|
||||||
if not self._framerate:
|
|
||||||
raise Error('frame rate not set')
|
|
||||||
return self._framerate
|
|
||||||
|
|
||||||
def setnframes(self, nframes):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
self._nframes = nframes
|
|
||||||
|
|
||||||
def getnframes(self):
|
|
||||||
return self._nframeswritten
|
|
||||||
|
|
||||||
def setcomptype(self, comptype, compname):
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
|
||||||
b'alaw', b'ALAW', b'G722'):
|
|
||||||
raise Error('unsupported compression type')
|
|
||||||
self._comptype = comptype
|
|
||||||
self._compname = compname
|
|
||||||
|
|
||||||
def getcomptype(self):
|
|
||||||
return self._comptype
|
|
||||||
|
|
||||||
def getcompname(self):
|
|
||||||
return self._compname
|
|
||||||
|
|
||||||
## def setversion(self, version):
|
|
||||||
## if self._nframeswritten:
|
|
||||||
## raise Error, 'cannot change parameters after starting to write'
|
|
||||||
## self._version = version
|
|
||||||
|
|
||||||
def setparams(self, params):
|
|
||||||
nchannels, sampwidth, framerate, nframes, comptype, compname = params
|
|
||||||
if self._nframeswritten:
|
|
||||||
raise Error('cannot change parameters after starting to write')
|
|
||||||
if comptype not in (b'NONE', b'ulaw', b'ULAW',
|
|
||||||
b'alaw', b'ALAW', b'G722'):
|
|
||||||
raise Error('unsupported compression type')
|
|
||||||
self.setnchannels(nchannels)
|
|
||||||
self.setsampwidth(sampwidth)
|
|
||||||
self.setframerate(framerate)
|
|
||||||
self.setnframes(nframes)
|
|
||||||
self.setcomptype(comptype, compname)
|
|
||||||
|
|
||||||
def getparams(self):
|
|
||||||
if not self._nchannels or not self._sampwidth or not self._framerate:
|
|
||||||
raise Error('not all parameters set')
|
|
||||||
return _aifc_params(self._nchannels, self._sampwidth, self._framerate,
|
|
||||||
self._nframes, self._comptype, self._compname)
|
|
||||||
|
|
||||||
def setmark(self, id, pos, name):
|
|
||||||
if id <= 0:
|
|
||||||
raise Error('marker ID must be > 0')
|
|
||||||
if pos < 0:
|
|
||||||
raise Error('marker position must be >= 0')
|
|
||||||
if not isinstance(name, bytes):
|
|
||||||
raise Error('marker name must be bytes')
|
|
||||||
for i in range(len(self._markers)):
|
|
||||||
if id == self._markers[i][0]:
|
|
||||||
self._markers[i] = id, pos, name
|
|
||||||
return
|
|
||||||
self._markers.append((id, pos, name))
|
|
||||||
|
|
||||||
def getmark(self, id):
|
|
||||||
for marker in self._markers:
|
|
||||||
if id == marker[0]:
|
|
||||||
return marker
|
|
||||||
raise Error('marker {0!r} does not exist'.format(id))
|
|
||||||
|
|
||||||
def getmarkers(self):
|
|
||||||
if len(self._markers) == 0:
|
|
||||||
return None
|
|
||||||
return self._markers
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
return self._nframeswritten
|
|
||||||
|
|
||||||
def writeframesraw(self, data):
|
|
||||||
if not isinstance(data, (bytes, bytearray)):
|
|
||||||
data = memoryview(data).cast('B')
|
|
||||||
self._ensure_header_written(len(data))
|
|
||||||
nframes = len(data) // (self._sampwidth * self._nchannels)
|
|
||||||
if self._convert:
|
|
||||||
data = self._convert(data)
|
|
||||||
self._file.write(data)
|
|
||||||
self._nframeswritten = self._nframeswritten + nframes
|
|
||||||
self._datawritten = self._datawritten + len(data)
|
|
||||||
|
|
||||||
def writeframes(self, data):
|
|
||||||
self.writeframesraw(data)
|
|
||||||
if self._nframeswritten != self._nframes or \
|
|
||||||
self._datalength != self._datawritten:
|
|
||||||
self._patchheader()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._file is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self._ensure_header_written(0)
|
|
||||||
if self._datawritten & 1:
|
|
||||||
# quick pad to even size
|
|
||||||
self._file.write(b'\x00')
|
|
||||||
self._datawritten = self._datawritten + 1
|
|
||||||
self._writemarkers()
|
|
||||||
if self._nframeswritten != self._nframes or \
|
|
||||||
self._datalength != self._datawritten or \
|
|
||||||
self._marklength:
|
|
||||||
self._patchheader()
|
|
||||||
finally:
|
|
||||||
# Prevent ref cycles
|
|
||||||
self._convert = None
|
|
||||||
f = self._file
|
|
||||||
self._file = None
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Internal methods.
|
|
||||||
#
|
|
||||||
|
|
||||||
def _lin2alaw(self, data):
|
|
||||||
import audioop
|
|
||||||
return audioop.lin2alaw(data, 2)
|
|
||||||
|
|
||||||
def _lin2ulaw(self, data):
|
|
||||||
import audioop
|
|
||||||
return audioop.lin2ulaw(data, 2)
|
|
||||||
|
|
||||||
def _lin2adpcm(self, data):
|
|
||||||
import audioop
|
|
||||||
if not hasattr(self, '_adpcmstate'):
|
|
||||||
self._adpcmstate = None
|
|
||||||
data, self._adpcmstate = audioop.lin2adpcm(data, 2, self._adpcmstate)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _ensure_header_written(self, datasize):
|
|
||||||
if not self._nframeswritten:
|
|
||||||
if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
|
|
||||||
if not self._sampwidth:
|
|
||||||
self._sampwidth = 2
|
|
||||||
if self._sampwidth != 2:
|
|
||||||
raise Error('sample width must be 2 when compressing '
|
|
||||||
'with ulaw/ULAW, alaw/ALAW or G7.22 (ADPCM)')
|
|
||||||
if not self._nchannels:
|
|
||||||
raise Error('# channels not specified')
|
|
||||||
if not self._sampwidth:
|
|
||||||
raise Error('sample width not specified')
|
|
||||||
if not self._framerate:
|
|
||||||
raise Error('sampling rate not specified')
|
|
||||||
self._write_header(datasize)
|
|
||||||
|
|
||||||
def _init_compression(self):
|
|
||||||
if self._comptype == b'G722':
|
|
||||||
self._convert = self._lin2adpcm
|
|
||||||
elif self._comptype in (b'ulaw', b'ULAW'):
|
|
||||||
self._convert = self._lin2ulaw
|
|
||||||
elif self._comptype in (b'alaw', b'ALAW'):
|
|
||||||
self._convert = self._lin2alaw
|
|
||||||
|
|
||||||
def _write_header(self, initlength):
|
|
||||||
if self._aifc and self._comptype != b'NONE':
|
|
||||||
self._init_compression()
|
|
||||||
self._file.write(b'FORM')
|
|
||||||
if not self._nframes:
|
|
||||||
self._nframes = initlength // (self._nchannels * self._sampwidth)
|
|
||||||
self._datalength = self._nframes * self._nchannels * self._sampwidth
|
|
||||||
if self._datalength & 1:
|
|
||||||
self._datalength = self._datalength + 1
|
|
||||||
if self._aifc:
|
|
||||||
if self._comptype in (b'ulaw', b'ULAW', b'alaw', b'ALAW'):
|
|
||||||
self._datalength = self._datalength // 2
|
|
||||||
if self._datalength & 1:
|
|
||||||
self._datalength = self._datalength + 1
|
|
||||||
elif self._comptype == b'G722':
|
|
||||||
self._datalength = (self._datalength + 3) // 4
|
|
||||||
if self._datalength & 1:
|
|
||||||
self._datalength = self._datalength + 1
|
|
||||||
try:
|
|
||||||
self._form_length_pos = self._file.tell()
|
|
||||||
except (AttributeError, OSError):
|
|
||||||
self._form_length_pos = None
|
|
||||||
commlength = self._write_form_length(self._datalength)
|
|
||||||
if self._aifc:
|
|
||||||
self._file.write(b'AIFC')
|
|
||||||
self._file.write(b'FVER')
|
|
||||||
_write_ulong(self._file, 4)
|
|
||||||
_write_ulong(self._file, self._version)
|
|
||||||
else:
|
|
||||||
self._file.write(b'AIFF')
|
|
||||||
self._file.write(b'COMM')
|
|
||||||
_write_ulong(self._file, commlength)
|
|
||||||
_write_short(self._file, self._nchannels)
|
|
||||||
if self._form_length_pos is not None:
|
|
||||||
self._nframes_pos = self._file.tell()
|
|
||||||
_write_ulong(self._file, self._nframes)
|
|
||||||
if self._comptype in (b'ULAW', b'ulaw', b'ALAW', b'alaw', b'G722'):
|
|
||||||
_write_short(self._file, 8)
|
|
||||||
else:
|
|
||||||
_write_short(self._file, self._sampwidth * 8)
|
|
||||||
_write_float(self._file, self._framerate)
|
|
||||||
if self._aifc:
|
|
||||||
self._file.write(self._comptype)
|
|
||||||
_write_string(self._file, self._compname)
|
|
||||||
self._file.write(b'SSND')
|
|
||||||
if self._form_length_pos is not None:
|
|
||||||
self._ssnd_length_pos = self._file.tell()
|
|
||||||
_write_ulong(self._file, self._datalength + 8)
|
|
||||||
_write_ulong(self._file, 0)
|
|
||||||
_write_ulong(self._file, 0)
|
|
||||||
|
|
||||||
def _write_form_length(self, datalength):
|
|
||||||
if self._aifc:
|
|
||||||
commlength = 18 + 5 + len(self._compname)
|
|
||||||
if commlength & 1:
|
|
||||||
commlength = commlength + 1
|
|
||||||
verslength = 12
|
|
||||||
else:
|
|
||||||
commlength = 18
|
|
||||||
verslength = 0
|
|
||||||
_write_ulong(self._file, 4 + verslength + self._marklength + \
|
|
||||||
8 + commlength + 16 + datalength)
|
|
||||||
return commlength
|
|
||||||
|
|
||||||
def _patchheader(self):
|
|
||||||
curpos = self._file.tell()
|
|
||||||
if self._datawritten & 1:
|
|
||||||
datalength = self._datawritten + 1
|
|
||||||
self._file.write(b'\x00')
|
|
||||||
else:
|
|
||||||
datalength = self._datawritten
|
|
||||||
if datalength == self._datalength and \
|
|
||||||
self._nframes == self._nframeswritten and \
|
|
||||||
self._marklength == 0:
|
|
||||||
self._file.seek(curpos, 0)
|
|
||||||
return
|
|
||||||
self._file.seek(self._form_length_pos, 0)
|
|
||||||
dummy = self._write_form_length(datalength)
|
|
||||||
self._file.seek(self._nframes_pos, 0)
|
|
||||||
_write_ulong(self._file, self._nframeswritten)
|
|
||||||
self._file.seek(self._ssnd_length_pos, 0)
|
|
||||||
_write_ulong(self._file, datalength + 8)
|
|
||||||
self._file.seek(curpos, 0)
|
|
||||||
self._nframes = self._nframeswritten
|
|
||||||
self._datalength = datalength
|
|
||||||
|
|
||||||
def _writemarkers(self):
|
|
||||||
if len(self._markers) == 0:
|
|
||||||
return
|
|
||||||
self._file.write(b'MARK')
|
|
||||||
length = 2
|
|
||||||
for marker in self._markers:
|
|
||||||
id, pos, name = marker
|
|
||||||
length = length + len(name) + 1 + 6
|
|
||||||
if len(name) & 1 == 0:
|
|
||||||
length = length + 1
|
|
||||||
_write_ulong(self._file, length)
|
|
||||||
self._marklength = length + 8
|
|
||||||
_write_short(self._file, len(self._markers))
|
|
||||||
for marker in self._markers:
|
|
||||||
id, pos, name = marker
|
|
||||||
_write_short(self._file, id)
|
|
||||||
_write_ulong(self._file, pos)
|
|
||||||
_write_string(self._file, name)
|
|
||||||
|
|
||||||
def open(f, mode=None):
|
|
||||||
if mode is None:
|
|
||||||
if hasattr(f, 'mode'):
|
|
||||||
mode = f.mode
|
|
||||||
else:
|
|
||||||
mode = 'rb'
|
|
||||||
if mode in ('r', 'rb'):
|
|
||||||
return Aifc_read(f)
|
|
||||||
elif mode in ('w', 'wb'):
|
|
||||||
return Aifc_write(f)
|
|
||||||
else:
|
|
||||||
raise Error("mode must be 'r', 'rb', 'w', or 'wb'")
|
|
||||||
|
|
||||||
def openfp(f, mode=None):
|
|
||||||
warnings.warn("aifc.openfp is deprecated since Python 3.7. "
|
|
||||||
"Use aifc.open instead.", DeprecationWarning, stacklevel=2)
|
|
||||||
return open(f, mode=mode)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
if not sys.argv[1:]:
|
|
||||||
sys.argv.append('/usr/demos/data/audio/bach.aiff')
|
|
||||||
fn = sys.argv[1]
|
|
||||||
with open(fn, 'r') as f:
|
|
||||||
print("Reading", fn)
|
|
||||||
print("nchannels =", f.getnchannels())
|
|
||||||
print("nframes =", f.getnframes())
|
|
||||||
print("sampwidth =", f.getsampwidth())
|
|
||||||
print("framerate =", f.getframerate())
|
|
||||||
print("comptype =", f.getcomptype())
|
|
||||||
print("compname =", f.getcompname())
|
|
||||||
if sys.argv[2:]:
|
|
||||||
gn = sys.argv[2]
|
|
||||||
print("Writing", gn)
|
|
||||||
with open(gn, 'w') as g:
|
|
||||||
g.setparams(f.getparams())
|
|
||||||
while 1:
|
|
||||||
data = f.readframes(1024)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
g.writeframes(data)
|
|
||||||
print("Done.")
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
import webbrowser
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
webbrowser.open("https://xkcd.com/353/")
|
|
||||||
|
|
||||||
def geohash(latitude, longitude, datedow):
|
|
||||||
'''Compute geohash() using the Munroe algorithm.
|
|
||||||
|
|
||||||
>>> geohash(37.421542, -122.085589, b'2005-05-26-10458.68')
|
|
||||||
37.857713 -122.544543
|
|
||||||
|
|
||||||
'''
|
|
||||||
# http://xkcd.com/426/
|
|
||||||
h = hashlib.md5(datedow).hexdigest()
|
|
||||||
p, q = [('%f' % float.fromhex('0.' + x)) for x in (h[:16], h[16:32])]
|
|
||||||
print('%d%s %d%s' % (latitude, p[1:], longitude, q[1:]))
|
|
||||||
2507
Lib/argparse.py
2507
Lib/argparse.py
File diff suppressed because it is too large
Load Diff
331
Lib/ast.py
331
Lib/ast.py
@@ -1,331 +0,0 @@
|
|||||||
"""
|
|
||||||
ast
|
|
||||||
~~~
|
|
||||||
|
|
||||||
The `ast` module helps Python applications to process trees of the Python
|
|
||||||
abstract syntax grammar. The abstract syntax itself might change with
|
|
||||||
each Python release; this module helps to find out programmatically what
|
|
||||||
the current grammar looks like and allows modifications of it.
|
|
||||||
|
|
||||||
An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
|
|
||||||
a flag to the `compile()` builtin function or by using the `parse()`
|
|
||||||
function from this module. The result will be a tree of objects whose
|
|
||||||
classes all inherit from `ast.AST`.
|
|
||||||
|
|
||||||
A modified abstract syntax tree can be compiled into a Python code object
|
|
||||||
using the built-in `compile()` function.
|
|
||||||
|
|
||||||
Additionally various helper functions are provided that make working with
|
|
||||||
the trees simpler. The main intention of the helper functions and this
|
|
||||||
module in general is to provide an easy to use interface for libraries
|
|
||||||
that work tightly with the python syntax (template engines for example).
|
|
||||||
|
|
||||||
|
|
||||||
:copyright: Copyright 2008 by Armin Ronacher.
|
|
||||||
:license: Python License.
|
|
||||||
"""
|
|
||||||
from _ast import *
|
|
||||||
|
|
||||||
|
|
||||||
def parse(source, filename='<unknown>', mode='exec'):
|
|
||||||
"""
|
|
||||||
Parse the source into an AST node.
|
|
||||||
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
|
|
||||||
"""
|
|
||||||
return compile(source, filename, mode, PyCF_ONLY_AST)
|
|
||||||
|
|
||||||
|
|
||||||
def literal_eval(node_or_string):
|
|
||||||
"""
|
|
||||||
Safely evaluate an expression node or a string containing a Python
|
|
||||||
expression. The string or node provided may only consist of the following
|
|
||||||
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
|
|
||||||
sets, booleans, and None.
|
|
||||||
"""
|
|
||||||
if isinstance(node_or_string, str):
|
|
||||||
node_or_string = parse(node_or_string, mode='eval')
|
|
||||||
if isinstance(node_or_string, Expression):
|
|
||||||
node_or_string = node_or_string.body
|
|
||||||
def _convert_num(node):
|
|
||||||
if isinstance(node, Constant):
|
|
||||||
if isinstance(node.value, (int, float, complex)):
|
|
||||||
return node.value
|
|
||||||
elif isinstance(node, Num):
|
|
||||||
return node.n
|
|
||||||
raise ValueError('malformed node or string: ' + repr(node))
|
|
||||||
def _convert_signed_num(node):
|
|
||||||
if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
|
|
||||||
operand = _convert_num(node.operand)
|
|
||||||
if isinstance(node.op, UAdd):
|
|
||||||
return + operand
|
|
||||||
else:
|
|
||||||
return - operand
|
|
||||||
return _convert_num(node)
|
|
||||||
def _convert(node):
|
|
||||||
if isinstance(node, Constant):
|
|
||||||
return node.value
|
|
||||||
elif isinstance(node, (Str, Bytes)):
|
|
||||||
return node.s
|
|
||||||
elif isinstance(node, Num):
|
|
||||||
return node.n
|
|
||||||
elif isinstance(node, Tuple):
|
|
||||||
return tuple(map(_convert, node.elts))
|
|
||||||
elif isinstance(node, List):
|
|
||||||
return list(map(_convert, node.elts))
|
|
||||||
elif isinstance(node, Set):
|
|
||||||
return set(map(_convert, node.elts))
|
|
||||||
elif isinstance(node, Dict):
|
|
||||||
return dict(zip(map(_convert, node.keys),
|
|
||||||
map(_convert, node.values)))
|
|
||||||
elif isinstance(node, NameConstant):
|
|
||||||
return node.value
|
|
||||||
elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
|
|
||||||
left = _convert_signed_num(node.left)
|
|
||||||
right = _convert_num(node.right)
|
|
||||||
if isinstance(left, (int, float)) and isinstance(right, complex):
|
|
||||||
if isinstance(node.op, Add):
|
|
||||||
return left + right
|
|
||||||
else:
|
|
||||||
return left - right
|
|
||||||
return _convert_signed_num(node)
|
|
||||||
return _convert(node_or_string)
|
|
||||||
|
|
||||||
|
|
||||||
def dump(node, annotate_fields=True, include_attributes=False):
|
|
||||||
"""
|
|
||||||
Return a formatted dump of the tree in *node*. This is mainly useful for
|
|
||||||
debugging purposes. The returned string will show the names and the values
|
|
||||||
for fields. This makes the code impossible to evaluate, so if evaluation is
|
|
||||||
wanted *annotate_fields* must be set to False. Attributes such as line
|
|
||||||
numbers and column offsets are not dumped by default. If this is wanted,
|
|
||||||
*include_attributes* can be set to True.
|
|
||||||
"""
|
|
||||||
def _format(node):
|
|
||||||
if isinstance(node, AST):
|
|
||||||
fields = [(a, _format(b)) for a, b in iter_fields(node)]
|
|
||||||
rv = '%s(%s' % (node.__class__.__name__, ', '.join(
|
|
||||||
('%s=%s' % field for field in fields)
|
|
||||||
if annotate_fields else
|
|
||||||
(b for a, b in fields)
|
|
||||||
))
|
|
||||||
if include_attributes and node._attributes:
|
|
||||||
rv += fields and ', ' or ' '
|
|
||||||
rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
|
|
||||||
for a in node._attributes)
|
|
||||||
return rv + ')'
|
|
||||||
elif isinstance(node, list):
|
|
||||||
return '[%s]' % ', '.join(_format(x) for x in node)
|
|
||||||
return repr(node)
|
|
||||||
if not isinstance(node, AST):
|
|
||||||
raise TypeError('expected AST, got %r' % node.__class__.__name__)
|
|
||||||
return _format(node)
|
|
||||||
|
|
||||||
|
|
||||||
def copy_location(new_node, old_node):
|
|
||||||
"""
|
|
||||||
Copy source location (`lineno` and `col_offset` attributes) from
|
|
||||||
*old_node* to *new_node* if possible, and return *new_node*.
|
|
||||||
"""
|
|
||||||
for attr in 'lineno', 'col_offset':
|
|
||||||
if attr in old_node._attributes and attr in new_node._attributes \
|
|
||||||
and hasattr(old_node, attr):
|
|
||||||
setattr(new_node, attr, getattr(old_node, attr))
|
|
||||||
return new_node
|
|
||||||
|
|
||||||
|
|
||||||
def fix_missing_locations(node):
|
|
||||||
"""
|
|
||||||
When you compile a node tree with compile(), the compiler expects lineno and
|
|
||||||
col_offset attributes for every node that supports them. This is rather
|
|
||||||
tedious to fill in for generated nodes, so this helper adds these attributes
|
|
||||||
recursively where not already set, by setting them to the values of the
|
|
||||||
parent node. It works recursively starting at *node*.
|
|
||||||
"""
|
|
||||||
def _fix(node, lineno, col_offset):
|
|
||||||
if 'lineno' in node._attributes:
|
|
||||||
if not hasattr(node, 'lineno'):
|
|
||||||
node.lineno = lineno
|
|
||||||
else:
|
|
||||||
lineno = node.lineno
|
|
||||||
if 'col_offset' in node._attributes:
|
|
||||||
if not hasattr(node, 'col_offset'):
|
|
||||||
node.col_offset = col_offset
|
|
||||||
else:
|
|
||||||
col_offset = node.col_offset
|
|
||||||
for child in iter_child_nodes(node):
|
|
||||||
_fix(child, lineno, col_offset)
|
|
||||||
_fix(node, 1, 0)
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
def increment_lineno(node, n=1):
|
|
||||||
"""
|
|
||||||
Increment the line number of each node in the tree starting at *node* by *n*.
|
|
||||||
This is useful to "move code" to a different location in a file.
|
|
||||||
"""
|
|
||||||
for child in walk(node):
|
|
||||||
if 'lineno' in child._attributes:
|
|
||||||
child.lineno = getattr(child, 'lineno', 0) + n
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
def iter_fields(node):
|
|
||||||
"""
|
|
||||||
Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
|
|
||||||
that is present on *node*.
|
|
||||||
"""
|
|
||||||
for field in node._fields:
|
|
||||||
try:
|
|
||||||
yield field, getattr(node, field)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def iter_child_nodes(node):
|
|
||||||
"""
|
|
||||||
Yield all direct child nodes of *node*, that is, all fields that are nodes
|
|
||||||
and all items of fields that are lists of nodes.
|
|
||||||
"""
|
|
||||||
for name, field in iter_fields(node):
|
|
||||||
if isinstance(field, AST):
|
|
||||||
yield field
|
|
||||||
elif isinstance(field, list):
|
|
||||||
for item in field:
|
|
||||||
if isinstance(item, AST):
|
|
||||||
yield item
|
|
||||||
|
|
||||||
|
|
||||||
def get_docstring(node, clean=True):
|
|
||||||
"""
|
|
||||||
Return the docstring for the given node or None if no docstring can
|
|
||||||
be found. If the node provided does not have docstrings a TypeError
|
|
||||||
will be raised.
|
|
||||||
|
|
||||||
If *clean* is `True`, all tabs are expanded to spaces and any whitespace
|
|
||||||
that can be uniformly removed from the second line onwards is removed.
|
|
||||||
"""
|
|
||||||
if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)):
|
|
||||||
raise TypeError("%r can't have docstrings" % node.__class__.__name__)
|
|
||||||
if not(node.body and isinstance(node.body[0], Expr)):
|
|
||||||
return None
|
|
||||||
node = node.body[0].value
|
|
||||||
if isinstance(node, Str):
|
|
||||||
text = node.s
|
|
||||||
elif isinstance(node, Constant) and isinstance(node.value, str):
|
|
||||||
text = node.value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
if clean:
|
|
||||||
import inspect
|
|
||||||
text = inspect.cleandoc(text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def walk(node):
|
|
||||||
"""
|
|
||||||
Recursively yield all descendant nodes in the tree starting at *node*
|
|
||||||
(including *node* itself), in no specified order. This is useful if you
|
|
||||||
only want to modify nodes in place and don't care about the context.
|
|
||||||
"""
|
|
||||||
from collections import deque
|
|
||||||
todo = deque([node])
|
|
||||||
while todo:
|
|
||||||
node = todo.popleft()
|
|
||||||
todo.extend(iter_child_nodes(node))
|
|
||||||
yield node
|
|
||||||
|
|
||||||
|
|
||||||
class NodeVisitor(object):
|
|
||||||
"""
|
|
||||||
A node visitor base class that walks the abstract syntax tree and calls a
|
|
||||||
visitor function for every node found. This function may return a value
|
|
||||||
which is forwarded by the `visit` method.
|
|
||||||
|
|
||||||
This class is meant to be subclassed, with the subclass adding visitor
|
|
||||||
methods.
|
|
||||||
|
|
||||||
Per default the visitor functions for the nodes are ``'visit_'`` +
|
|
||||||
class name of the node. So a `TryFinally` node visit function would
|
|
||||||
be `visit_TryFinally`. This behavior can be changed by overriding
|
|
||||||
the `visit` method. If no visitor function exists for a node
|
|
||||||
(return value `None`) the `generic_visit` visitor is used instead.
|
|
||||||
|
|
||||||
Don't use the `NodeVisitor` if you want to apply changes to nodes during
|
|
||||||
traversing. For this a special visitor exists (`NodeTransformer`) that
|
|
||||||
allows modifications.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def visit(self, node):
|
|
||||||
"""Visit a node."""
|
|
||||||
method = 'visit_' + node.__class__.__name__
|
|
||||||
visitor = getattr(self, method, self.generic_visit)
|
|
||||||
return visitor(node)
|
|
||||||
|
|
||||||
def generic_visit(self, node):
|
|
||||||
"""Called if no explicit visitor function exists for a node."""
|
|
||||||
for field, value in iter_fields(node):
|
|
||||||
if isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
if isinstance(item, AST):
|
|
||||||
self.visit(item)
|
|
||||||
elif isinstance(value, AST):
|
|
||||||
self.visit(value)
|
|
||||||
|
|
||||||
|
|
||||||
class NodeTransformer(NodeVisitor):
|
|
||||||
"""
|
|
||||||
A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
|
|
||||||
allows modification of nodes.
|
|
||||||
|
|
||||||
The `NodeTransformer` will walk the AST and use the return value of the
|
|
||||||
visitor methods to replace or remove the old node. If the return value of
|
|
||||||
the visitor method is ``None``, the node will be removed from its location,
|
|
||||||
otherwise it is replaced with the return value. The return value may be the
|
|
||||||
original node in which case no replacement takes place.
|
|
||||||
|
|
||||||
Here is an example transformer that rewrites all occurrences of name lookups
|
|
||||||
(``foo``) to ``data['foo']``::
|
|
||||||
|
|
||||||
class RewriteName(NodeTransformer):
|
|
||||||
|
|
||||||
def visit_Name(self, node):
|
|
||||||
return copy_location(Subscript(
|
|
||||||
value=Name(id='data', ctx=Load()),
|
|
||||||
slice=Index(value=Str(s=node.id)),
|
|
||||||
ctx=node.ctx
|
|
||||||
), node)
|
|
||||||
|
|
||||||
Keep in mind that if the node you're operating on has child nodes you must
|
|
||||||
either transform the child nodes yourself or call the :meth:`generic_visit`
|
|
||||||
method for the node first.
|
|
||||||
|
|
||||||
For nodes that were part of a collection of statements (that applies to all
|
|
||||||
statement nodes), the visitor may also return a list of nodes rather than
|
|
||||||
just a single node.
|
|
||||||
|
|
||||||
Usually you use the transformer like this::
|
|
||||||
|
|
||||||
node = YourTransformer().visit(node)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def generic_visit(self, node):
|
|
||||||
for field, old_value in iter_fields(node):
|
|
||||||
if isinstance(old_value, list):
|
|
||||||
new_values = []
|
|
||||||
for value in old_value:
|
|
||||||
if isinstance(value, AST):
|
|
||||||
value = self.visit(value)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
elif not isinstance(value, AST):
|
|
||||||
new_values.extend(value)
|
|
||||||
continue
|
|
||||||
new_values.append(value)
|
|
||||||
old_value[:] = new_values
|
|
||||||
elif isinstance(old_value, AST):
|
|
||||||
new_node = self.visit(old_value)
|
|
||||||
if new_node is None:
|
|
||||||
delattr(node, field)
|
|
||||||
else:
|
|
||||||
setattr(node, field, new_node)
|
|
||||||
return node
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
"""The asyncio package, tracking PEP 3156."""
|
|
||||||
|
|
||||||
# flake8: noqa
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import selectors
|
|
||||||
# XXX RustPython TODO: _overlapped
|
|
||||||
if sys.platform == 'win32' and False:
|
|
||||||
# Similar thing for _overlapped.
|
|
||||||
try:
|
|
||||||
from . import _overlapped
|
|
||||||
except ImportError:
|
|
||||||
import _overlapped # Will also be exported.
|
|
||||||
|
|
||||||
|
|
||||||
# This relies on each of the submodules having an __all__ variable.
|
|
||||||
from .base_events import *
|
|
||||||
from .coroutines import *
|
|
||||||
from .events import *
|
|
||||||
from .futures import *
|
|
||||||
from .locks import *
|
|
||||||
from .protocols import *
|
|
||||||
from .runners import *
|
|
||||||
from .queues import *
|
|
||||||
from .streams import *
|
|
||||||
from .subprocess import *
|
|
||||||
from .tasks import *
|
|
||||||
from .transports import *
|
|
||||||
|
|
||||||
__all__ = (base_events.__all__ +
|
|
||||||
coroutines.__all__ +
|
|
||||||
events.__all__ +
|
|
||||||
futures.__all__ +
|
|
||||||
locks.__all__ +
|
|
||||||
protocols.__all__ +
|
|
||||||
runners.__all__ +
|
|
||||||
queues.__all__ +
|
|
||||||
streams.__all__ +
|
|
||||||
subprocess.__all__ +
|
|
||||||
tasks.__all__ +
|
|
||||||
transports.__all__)
|
|
||||||
|
|
||||||
if sys.platform == 'win32': # pragma: no cover
|
|
||||||
from .windows_events import *
|
|
||||||
__all__ += windows_events.__all__
|
|
||||||
else:
|
|
||||||
from .unix_events import * # pragma: no cover
|
|
||||||
__all__ += unix_events.__all__
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,71 +0,0 @@
|
|||||||
__all__ = []
|
|
||||||
|
|
||||||
import concurrent.futures._base
|
|
||||||
import reprlib
|
|
||||||
|
|
||||||
from . import events
|
|
||||||
|
|
||||||
Error = concurrent.futures._base.Error
|
|
||||||
CancelledError = concurrent.futures.CancelledError
|
|
||||||
TimeoutError = concurrent.futures.TimeoutError
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidStateError(Error):
|
|
||||||
"""The operation is not allowed in this state."""
|
|
||||||
|
|
||||||
|
|
||||||
# States for Future.
|
|
||||||
_PENDING = 'PENDING'
|
|
||||||
_CANCELLED = 'CANCELLED'
|
|
||||||
_FINISHED = 'FINISHED'
|
|
||||||
|
|
||||||
|
|
||||||
def isfuture(obj):
|
|
||||||
"""Check for a Future.
|
|
||||||
|
|
||||||
This returns True when obj is a Future instance or is advertising
|
|
||||||
itself as duck-type compatible by setting _asyncio_future_blocking.
|
|
||||||
See comment in Future for more details.
|
|
||||||
"""
|
|
||||||
return (hasattr(obj.__class__, '_asyncio_future_blocking') and
|
|
||||||
obj._asyncio_future_blocking is not None)
|
|
||||||
|
|
||||||
|
|
||||||
def _format_callbacks(cb):
|
|
||||||
"""helper function for Future.__repr__"""
|
|
||||||
size = len(cb)
|
|
||||||
if not size:
|
|
||||||
cb = ''
|
|
||||||
|
|
||||||
def format_cb(callback):
|
|
||||||
return events._format_callback_source(callback, ())
|
|
||||||
|
|
||||||
if size == 1:
|
|
||||||
cb = format_cb(cb[0])
|
|
||||||
elif size == 2:
|
|
||||||
cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
|
|
||||||
elif size > 2:
|
|
||||||
cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
|
|
||||||
size - 2,
|
|
||||||
format_cb(cb[-1]))
|
|
||||||
return 'cb=[%s]' % cb
|
|
||||||
|
|
||||||
|
|
||||||
def _future_repr_info(future):
|
|
||||||
# (Future) -> str
|
|
||||||
"""helper function for Future.__repr__"""
|
|
||||||
info = [future._state.lower()]
|
|
||||||
if future._state == _FINISHED:
|
|
||||||
if future._exception is not None:
|
|
||||||
info.append('exception={!r}'.format(future._exception))
|
|
||||||
else:
|
|
||||||
# use reprlib to limit the length of the output, especially
|
|
||||||
# for very long strings
|
|
||||||
result = reprlib.repr(future._result)
|
|
||||||
info.append('result={}'.format(result))
|
|
||||||
if future._callbacks:
|
|
||||||
info.append(_format_callbacks(future._callbacks))
|
|
||||||
if future._source_traceback:
|
|
||||||
frame = future._source_traceback[-1]
|
|
||||||
info.append('created at %s:%s' % (frame[0], frame[1]))
|
|
||||||
return info
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
import collections
|
|
||||||
import subprocess
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import protocols
|
|
||||||
from . import transports
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
class BaseSubprocessTransport(transports.SubprocessTransport):
|
|
||||||
|
|
||||||
def __init__(self, loop, protocol, args, shell,
|
|
||||||
stdin, stdout, stderr, bufsize,
|
|
||||||
waiter=None, extra=None, **kwargs):
|
|
||||||
super().__init__(extra)
|
|
||||||
self._closed = False
|
|
||||||
self._protocol = protocol
|
|
||||||
self._loop = loop
|
|
||||||
self._proc = None
|
|
||||||
self._pid = None
|
|
||||||
self._returncode = None
|
|
||||||
self._exit_waiters = []
|
|
||||||
self._pending_calls = collections.deque()
|
|
||||||
self._pipes = {}
|
|
||||||
self._finished = False
|
|
||||||
|
|
||||||
if stdin == subprocess.PIPE:
|
|
||||||
self._pipes[0] = None
|
|
||||||
if stdout == subprocess.PIPE:
|
|
||||||
self._pipes[1] = None
|
|
||||||
if stderr == subprocess.PIPE:
|
|
||||||
self._pipes[2] = None
|
|
||||||
|
|
||||||
# Create the child process: set the _proc attribute
|
|
||||||
try:
|
|
||||||
self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
|
|
||||||
stderr=stderr, bufsize=bufsize, **kwargs)
|
|
||||||
except:
|
|
||||||
self.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
self._pid = self._proc.pid
|
|
||||||
self._extra['subprocess'] = self._proc
|
|
||||||
|
|
||||||
if self._loop.get_debug():
|
|
||||||
if isinstance(args, (bytes, str)):
|
|
||||||
program = args
|
|
||||||
else:
|
|
||||||
program = args[0]
|
|
||||||
logger.debug('process %r created: pid %s',
|
|
||||||
program, self._pid)
|
|
||||||
|
|
||||||
self._loop.create_task(self._connect_pipes(waiter))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = [self.__class__.__name__]
|
|
||||||
if self._closed:
|
|
||||||
info.append('closed')
|
|
||||||
if self._pid is not None:
|
|
||||||
info.append('pid=%s' % self._pid)
|
|
||||||
if self._returncode is not None:
|
|
||||||
info.append('returncode=%s' % self._returncode)
|
|
||||||
elif self._pid is not None:
|
|
||||||
info.append('running')
|
|
||||||
else:
|
|
||||||
info.append('not started')
|
|
||||||
|
|
||||||
stdin = self._pipes.get(0)
|
|
||||||
if stdin is not None:
|
|
||||||
info.append('stdin=%s' % stdin.pipe)
|
|
||||||
|
|
||||||
stdout = self._pipes.get(1)
|
|
||||||
stderr = self._pipes.get(2)
|
|
||||||
if stdout is not None and stderr is stdout:
|
|
||||||
info.append('stdout=stderr=%s' % stdout.pipe)
|
|
||||||
else:
|
|
||||||
if stdout is not None:
|
|
||||||
info.append('stdout=%s' % stdout.pipe)
|
|
||||||
if stderr is not None:
|
|
||||||
info.append('stderr=%s' % stderr.pipe)
|
|
||||||
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_protocol(self, protocol):
|
|
||||||
self._protocol = protocol
|
|
||||||
|
|
||||||
def get_protocol(self):
|
|
||||||
return self._protocol
|
|
||||||
|
|
||||||
def is_closing(self):
|
|
||||||
return self._closed
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._closed:
|
|
||||||
return
|
|
||||||
self._closed = True
|
|
||||||
|
|
||||||
for proto in self._pipes.values():
|
|
||||||
if proto is None:
|
|
||||||
continue
|
|
||||||
proto.pipe.close()
|
|
||||||
|
|
||||||
if (self._proc is not None
|
|
||||||
# the child process finished?
|
|
||||||
and self._returncode is None
|
|
||||||
# the child process finished but the transport was not notified yet?
|
|
||||||
and self._proc.poll() is None
|
|
||||||
):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning('Close running child process: kill %r', self)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._proc.kill()
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Don't clear the _proc reference yet: _post_init() may still run
|
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
|
||||||
if not self._closed:
|
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def get_pid(self):
|
|
||||||
return self._pid
|
|
||||||
|
|
||||||
def get_returncode(self):
|
|
||||||
return self._returncode
|
|
||||||
|
|
||||||
def get_pipe_transport(self, fd):
|
|
||||||
if fd in self._pipes:
|
|
||||||
return self._pipes[fd].pipe
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _check_proc(self):
|
|
||||||
if self._proc is None:
|
|
||||||
raise ProcessLookupError()
|
|
||||||
|
|
||||||
def send_signal(self, signal):
|
|
||||||
self._check_proc()
|
|
||||||
self._proc.send_signal(signal)
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
self._check_proc()
|
|
||||||
self._proc.terminate()
|
|
||||||
|
|
||||||
def kill(self):
|
|
||||||
self._check_proc()
|
|
||||||
self._proc.kill()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _connect_pipes(self, waiter):
|
|
||||||
try:
|
|
||||||
proc = self._proc
|
|
||||||
loop = self._loop
|
|
||||||
|
|
||||||
if proc.stdin is not None:
|
|
||||||
_, pipe = yield from loop.connect_write_pipe(
|
|
||||||
lambda: WriteSubprocessPipeProto(self, 0),
|
|
||||||
proc.stdin)
|
|
||||||
self._pipes[0] = pipe
|
|
||||||
|
|
||||||
if proc.stdout is not None:
|
|
||||||
_, pipe = yield from loop.connect_read_pipe(
|
|
||||||
lambda: ReadSubprocessPipeProto(self, 1),
|
|
||||||
proc.stdout)
|
|
||||||
self._pipes[1] = pipe
|
|
||||||
|
|
||||||
if proc.stderr is not None:
|
|
||||||
_, pipe = yield from loop.connect_read_pipe(
|
|
||||||
lambda: ReadSubprocessPipeProto(self, 2),
|
|
||||||
proc.stderr)
|
|
||||||
self._pipes[2] = pipe
|
|
||||||
|
|
||||||
assert self._pending_calls is not None
|
|
||||||
|
|
||||||
loop.call_soon(self._protocol.connection_made, self)
|
|
||||||
for callback, data in self._pending_calls:
|
|
||||||
loop.call_soon(callback, *data)
|
|
||||||
self._pending_calls = None
|
|
||||||
except Exception as exc:
|
|
||||||
if waiter is not None and not waiter.cancelled():
|
|
||||||
waiter.set_exception(exc)
|
|
||||||
else:
|
|
||||||
if waiter is not None and not waiter.cancelled():
|
|
||||||
waiter.set_result(None)
|
|
||||||
|
|
||||||
def _call(self, cb, *data):
|
|
||||||
if self._pending_calls is not None:
|
|
||||||
self._pending_calls.append((cb, data))
|
|
||||||
else:
|
|
||||||
self._loop.call_soon(cb, *data)
|
|
||||||
|
|
||||||
def _pipe_connection_lost(self, fd, exc):
|
|
||||||
self._call(self._protocol.pipe_connection_lost, fd, exc)
|
|
||||||
self._try_finish()
|
|
||||||
|
|
||||||
def _pipe_data_received(self, fd, data):
|
|
||||||
self._call(self._protocol.pipe_data_received, fd, data)
|
|
||||||
|
|
||||||
def _process_exited(self, returncode):
|
|
||||||
assert returncode is not None, returncode
|
|
||||||
assert self._returncode is None, self._returncode
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.info('%r exited with return code %r',
|
|
||||||
self, returncode)
|
|
||||||
self._returncode = returncode
|
|
||||||
if self._proc.returncode is None:
|
|
||||||
# asyncio uses a child watcher: copy the status into the Popen
|
|
||||||
# object. On Python 3.6, it is required to avoid a ResourceWarning.
|
|
||||||
self._proc.returncode = returncode
|
|
||||||
self._call(self._protocol.process_exited)
|
|
||||||
self._try_finish()
|
|
||||||
|
|
||||||
# wake up futures waiting for wait()
|
|
||||||
for waiter in self._exit_waiters:
|
|
||||||
if not waiter.cancelled():
|
|
||||||
waiter.set_result(returncode)
|
|
||||||
self._exit_waiters = None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wait(self):
|
|
||||||
"""Wait until the process exit and return the process return code.
|
|
||||||
|
|
||||||
This method is a coroutine."""
|
|
||||||
if self._returncode is not None:
|
|
||||||
return self._returncode
|
|
||||||
|
|
||||||
waiter = self._loop.create_future()
|
|
||||||
self._exit_waiters.append(waiter)
|
|
||||||
return (yield from waiter)
|
|
||||||
|
|
||||||
def _try_finish(self):
|
|
||||||
assert not self._finished
|
|
||||||
if self._returncode is None:
|
|
||||||
return
|
|
||||||
if all(p is not None and p.disconnected
|
|
||||||
for p in self._pipes.values()):
|
|
||||||
self._finished = True
|
|
||||||
self._call(self._call_connection_lost, None)
|
|
||||||
|
|
||||||
def _call_connection_lost(self, exc):
|
|
||||||
try:
|
|
||||||
self._protocol.connection_lost(exc)
|
|
||||||
finally:
|
|
||||||
self._loop = None
|
|
||||||
self._proc = None
|
|
||||||
self._protocol = None
|
|
||||||
|
|
||||||
|
|
||||||
class WriteSubprocessPipeProto(protocols.BaseProtocol):
|
|
||||||
|
|
||||||
def __init__(self, proc, fd):
|
|
||||||
self.proc = proc
|
|
||||||
self.fd = fd
|
|
||||||
self.pipe = None
|
|
||||||
self.disconnected = False
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
self.pipe = transport
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<%s fd=%s pipe=%r>'
|
|
||||||
% (self.__class__.__name__, self.fd, self.pipe))
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
self.disconnected = True
|
|
||||||
self.proc._pipe_connection_lost(self.fd, exc)
|
|
||||||
self.proc = None
|
|
||||||
|
|
||||||
def pause_writing(self):
|
|
||||||
self.proc._protocol.pause_writing()
|
|
||||||
|
|
||||||
def resume_writing(self):
|
|
||||||
self.proc._protocol.resume_writing()
|
|
||||||
|
|
||||||
|
|
||||||
class ReadSubprocessPipeProto(WriteSubprocessPipeProto,
|
|
||||||
protocols.Protocol):
|
|
||||||
|
|
||||||
def data_received(self, data):
|
|
||||||
self.proc._pipe_data_received(self.fd, data)
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import linecache
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from . import base_futures
|
|
||||||
from . import coroutines
|
|
||||||
|
|
||||||
|
|
||||||
def _task_repr_info(task):
|
|
||||||
info = base_futures._future_repr_info(task)
|
|
||||||
|
|
||||||
if task._must_cancel:
|
|
||||||
# replace status
|
|
||||||
info[0] = 'cancelling'
|
|
||||||
|
|
||||||
coro = coroutines._format_coroutine(task._coro)
|
|
||||||
info.insert(1, 'coro=<%s>' % coro)
|
|
||||||
|
|
||||||
if task._fut_waiter is not None:
|
|
||||||
info.insert(2, 'wait_for=%r' % task._fut_waiter)
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
def _task_get_stack(task, limit):
|
|
||||||
frames = []
|
|
||||||
try:
|
|
||||||
# 'async def' coroutines
|
|
||||||
f = task._coro.cr_frame
|
|
||||||
except AttributeError:
|
|
||||||
f = task._coro.gi_frame
|
|
||||||
if f is not None:
|
|
||||||
while f is not None:
|
|
||||||
if limit is not None:
|
|
||||||
if limit <= 0:
|
|
||||||
break
|
|
||||||
limit -= 1
|
|
||||||
frames.append(f)
|
|
||||||
f = f.f_back
|
|
||||||
frames.reverse()
|
|
||||||
elif task._exception is not None:
|
|
||||||
tb = task._exception.__traceback__
|
|
||||||
while tb is not None:
|
|
||||||
if limit is not None:
|
|
||||||
if limit <= 0:
|
|
||||||
break
|
|
||||||
limit -= 1
|
|
||||||
frames.append(tb.tb_frame)
|
|
||||||
tb = tb.tb_next
|
|
||||||
return frames
|
|
||||||
|
|
||||||
|
|
||||||
def _task_print_stack(task, limit, file):
|
|
||||||
extracted_list = []
|
|
||||||
checked = set()
|
|
||||||
for f in task.get_stack(limit=limit):
|
|
||||||
lineno = f.f_lineno
|
|
||||||
co = f.f_code
|
|
||||||
filename = co.co_filename
|
|
||||||
name = co.co_name
|
|
||||||
if filename not in checked:
|
|
||||||
checked.add(filename)
|
|
||||||
linecache.checkcache(filename)
|
|
||||||
line = linecache.getline(filename, lineno, f.f_globals)
|
|
||||||
extracted_list.append((filename, lineno, name, line))
|
|
||||||
exc = task._exception
|
|
||||||
if not extracted_list:
|
|
||||||
print('No stack for %r' % task, file=file)
|
|
||||||
elif exc is not None:
|
|
||||||
print('Traceback for %r (most recent call last):' % task,
|
|
||||||
file=file)
|
|
||||||
else:
|
|
||||||
print('Stack for %r (most recent call last):' % task,
|
|
||||||
file=file)
|
|
||||||
traceback.print_list(extracted_list, file=file)
|
|
||||||
if exc is not None:
|
|
||||||
for line in traceback.format_exception_only(exc.__class__, exc):
|
|
||||||
print(line, file=file, end='')
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
"""Compatibility helpers for the different Python versions."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
PY34 = sys.version_info >= (3, 4)
|
|
||||||
PY35 = sys.version_info >= (3, 5)
|
|
||||||
PY352 = sys.version_info >= (3, 5, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_list_bytes(list_of_data):
|
|
||||||
"""Concatenate a sequence of bytes-like objects."""
|
|
||||||
if not PY34:
|
|
||||||
# On Python 3.3 and older, bytes.join() doesn't handle
|
|
||||||
# memoryview.
|
|
||||||
list_of_data = (
|
|
||||||
bytes(data) if isinstance(data, memoryview) else data
|
|
||||||
for data in list_of_data)
|
|
||||||
return b''.join(list_of_data)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
"""Constants."""
|
|
||||||
|
|
||||||
# After the connection is lost, log warnings after this many write()s.
|
|
||||||
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
|
|
||||||
|
|
||||||
# Seconds to wait before retrying accept().
|
|
||||||
ACCEPT_RETRY_DELAY = 1
|
|
||||||
@@ -1,344 +0,0 @@
|
|||||||
__all__ = ['coroutine',
|
|
||||||
'iscoroutinefunction', 'iscoroutine']
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
import opcode
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import types
|
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import base_futures
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
# Opcode of "yield from" instruction
|
|
||||||
_YIELD_FROM = opcode.opmap['YIELD_FROM']
|
|
||||||
|
|
||||||
# If you set _DEBUG to true, @coroutine will wrap the resulting
|
|
||||||
# generator objects in a CoroWrapper instance (defined below). That
|
|
||||||
# instance will log a message when the generator is never iterated
|
|
||||||
# over, which may happen when you forget to use "yield from" with a
|
|
||||||
# coroutine call. Note that the value of the _DEBUG flag is taken
|
|
||||||
# when the decorator is used, so to be of any use it must be set
|
|
||||||
# before you define your coroutines. A downside of using this feature
|
|
||||||
# is that tracebacks show entries for the CoroWrapper.__next__ method
|
|
||||||
# when _DEBUG is true.
|
|
||||||
_DEBUG = (not sys.flags.ignore_environment and
|
|
||||||
bool(os.environ.get('PYTHONASYNCIODEBUG')))
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
_types_coroutine = types.coroutine
|
|
||||||
_types_CoroutineType = types.CoroutineType
|
|
||||||
except AttributeError:
|
|
||||||
# Python 3.4
|
|
||||||
_types_coroutine = None
|
|
||||||
_types_CoroutineType = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
_inspect_iscoroutinefunction = inspect.iscoroutinefunction
|
|
||||||
except AttributeError:
|
|
||||||
# Python 3.4
|
|
||||||
_inspect_iscoroutinefunction = lambda func: False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from collections.abc import Coroutine as _CoroutineABC, \
|
|
||||||
Awaitable as _AwaitableABC
|
|
||||||
except ImportError:
|
|
||||||
_CoroutineABC = _AwaitableABC = None
|
|
||||||
|
|
||||||
|
|
||||||
# Check for CPython issue #21209
|
|
||||||
def has_yield_from_bug():
|
|
||||||
class MyGen:
|
|
||||||
def __init__(self):
|
|
||||||
self.send_args = None
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
def __next__(self):
|
|
||||||
return 42
|
|
||||||
def send(self, *what):
|
|
||||||
self.send_args = what
|
|
||||||
return None
|
|
||||||
def yield_from_gen(gen):
|
|
||||||
yield from gen
|
|
||||||
value = (1, 2, 3)
|
|
||||||
gen = MyGen()
|
|
||||||
coro = yield_from_gen(gen)
|
|
||||||
next(coro)
|
|
||||||
coro.send(value)
|
|
||||||
return gen.send_args != (value,)
|
|
||||||
_YIELD_FROM_BUG = has_yield_from_bug()
|
|
||||||
del has_yield_from_bug
|
|
||||||
|
|
||||||
|
|
||||||
def debug_wrapper(gen):
|
|
||||||
# This function is called from 'sys.set_coroutine_wrapper'.
|
|
||||||
# We only wrap here coroutines defined via 'async def' syntax.
|
|
||||||
# Generator-based coroutines are wrapped in @coroutine
|
|
||||||
# decorator.
|
|
||||||
return CoroWrapper(gen, None)
|
|
||||||
|
|
||||||
|
|
||||||
class CoroWrapper:
|
|
||||||
# Wrapper for coroutine object in _DEBUG mode.
|
|
||||||
|
|
||||||
def __init__(self, gen, func=None):
|
|
||||||
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
|
|
||||||
self.gen = gen
|
|
||||||
self.func = func # Used to unwrap @coroutine decorator
|
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
|
||||||
self.__name__ = getattr(gen, '__name__', None)
|
|
||||||
self.__qualname__ = getattr(gen, '__qualname__', None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
coro_repr = _format_coroutine(self)
|
|
||||||
if self._source_traceback:
|
|
||||||
frame = self._source_traceback[-1]
|
|
||||||
coro_repr += ', created at %s:%s' % (frame[0], frame[1])
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, coro_repr)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
return self.gen.send(None)
|
|
||||||
|
|
||||||
if _YIELD_FROM_BUG:
|
|
||||||
# For for CPython issue #21209: using "yield from" and a custom
|
|
||||||
# generator, generator.send(tuple) unpacks the tuple instead of passing
|
|
||||||
# the tuple unchanged. Check if the caller is a generator using "yield
|
|
||||||
# from" to decide if the parameter should be unpacked or not.
|
|
||||||
def send(self, *value):
|
|
||||||
frame = sys._getframe()
|
|
||||||
caller = frame.f_back
|
|
||||||
assert caller.f_lasti >= 0
|
|
||||||
if caller.f_code.co_code[caller.f_lasti] != _YIELD_FROM:
|
|
||||||
value = value[0]
|
|
||||||
return self.gen.send(value)
|
|
||||||
else:
|
|
||||||
def send(self, value):
|
|
||||||
return self.gen.send(value)
|
|
||||||
|
|
||||||
def throw(self, type, value=None, traceback=None):
|
|
||||||
return self.gen.throw(type, value, traceback)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
return self.gen.close()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_frame(self):
|
|
||||||
return self.gen.gi_frame
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_running(self):
|
|
||||||
return self.gen.gi_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_code(self):
|
|
||||||
return self.gen.gi_code
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
cr_await = getattr(self.gen, 'cr_await', None)
|
|
||||||
if cr_await is not None:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Cannot await on coroutine {!r} while it's "
|
|
||||||
"awaiting for {!r}".format(self.gen, cr_await))
|
|
||||||
return self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gi_yieldfrom(self):
|
|
||||||
return self.gen.gi_yieldfrom
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_await(self):
|
|
||||||
return self.gen.cr_await
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_running(self):
|
|
||||||
return self.gen.cr_running
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_code(self):
|
|
||||||
return self.gen.cr_code
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cr_frame(self):
|
|
||||||
return self.gen.cr_frame
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
# Be careful accessing self.gen.frame -- self.gen might not exist.
|
|
||||||
gen = getattr(self, 'gen', None)
|
|
||||||
frame = getattr(gen, 'gi_frame', None)
|
|
||||||
if frame is None:
|
|
||||||
frame = getattr(gen, 'cr_frame', None)
|
|
||||||
if frame is not None and frame.f_lasti == -1:
|
|
||||||
msg = '%r was never yielded from' % self
|
|
||||||
tb = getattr(self, '_source_traceback', ())
|
|
||||||
if tb:
|
|
||||||
tb = ''.join(traceback.format_list(tb))
|
|
||||||
msg += ('\nCoroutine object created at '
|
|
||||||
'(most recent call last):\n')
|
|
||||||
msg += tb.rstrip()
|
|
||||||
logger.error(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def coroutine(func):
|
|
||||||
"""Decorator to mark coroutines.
|
|
||||||
|
|
||||||
If the coroutine is not yielded from before it is destroyed,
|
|
||||||
an error message is logged.
|
|
||||||
"""
|
|
||||||
if _inspect_iscoroutinefunction(func):
|
|
||||||
# In Python 3.5 that's all we need to do for coroutines
|
|
||||||
# defiend with "async def".
|
|
||||||
# Wrapping in CoroWrapper will happen via
|
|
||||||
# 'sys.set_coroutine_wrapper' function.
|
|
||||||
return func
|
|
||||||
|
|
||||||
if inspect.isgeneratorfunction(func):
|
|
||||||
coro = func
|
|
||||||
else:
|
|
||||||
@functools.wraps(func)
|
|
||||||
def coro(*args, **kw):
|
|
||||||
res = func(*args, **kw)
|
|
||||||
if (base_futures.isfuture(res) or inspect.isgenerator(res) or
|
|
||||||
isinstance(res, CoroWrapper)):
|
|
||||||
res = yield from res
|
|
||||||
elif _AwaitableABC is not None:
|
|
||||||
# If 'func' returns an Awaitable (new in 3.5) we
|
|
||||||
# want to run it.
|
|
||||||
try:
|
|
||||||
await_meth = res.__await__
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if isinstance(res, _AwaitableABC):
|
|
||||||
res = yield from await_meth()
|
|
||||||
return res
|
|
||||||
|
|
||||||
if not _DEBUG:
|
|
||||||
if _types_coroutine is None:
|
|
||||||
wrapper = coro
|
|
||||||
else:
|
|
||||||
wrapper = _types_coroutine(coro)
|
|
||||||
else:
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwds):
|
|
||||||
w = CoroWrapper(coro(*args, **kwds), func=func)
|
|
||||||
if w._source_traceback:
|
|
||||||
del w._source_traceback[-1]
|
|
||||||
# Python < 3.5 does not implement __qualname__
|
|
||||||
# on generator objects, so we set it manually.
|
|
||||||
# We use getattr as some callables (such as
|
|
||||||
# functools.partial may lack __qualname__).
|
|
||||||
w.__name__ = getattr(func, '__name__', None)
|
|
||||||
w.__qualname__ = getattr(func, '__qualname__', None)
|
|
||||||
return w
|
|
||||||
|
|
||||||
wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction().
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
# A marker for iscoroutinefunction.
|
|
||||||
_is_coroutine = object()
|
|
||||||
|
|
||||||
|
|
||||||
def iscoroutinefunction(func):
|
|
||||||
"""Return True if func is a decorated coroutine function."""
|
|
||||||
return (getattr(func, '_is_coroutine', None) is _is_coroutine or
|
|
||||||
_inspect_iscoroutinefunction(func))
|
|
||||||
|
|
||||||
|
|
||||||
_COROUTINE_TYPES = (types.GeneratorType, CoroWrapper)
|
|
||||||
if _CoroutineABC is not None:
|
|
||||||
_COROUTINE_TYPES += (_CoroutineABC,)
|
|
||||||
if _types_CoroutineType is not None:
|
|
||||||
# Prioritize native coroutine check to speed-up
|
|
||||||
# asyncio.iscoroutine.
|
|
||||||
_COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES
|
|
||||||
|
|
||||||
|
|
||||||
def iscoroutine(obj):
|
|
||||||
"""Return True if obj is a coroutine object."""
|
|
||||||
return isinstance(obj, _COROUTINE_TYPES)
|
|
||||||
|
|
||||||
|
|
||||||
def _format_coroutine(coro):
|
|
||||||
assert iscoroutine(coro)
|
|
||||||
|
|
||||||
if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'):
|
|
||||||
# Most likely a built-in type or a Cython coroutine.
|
|
||||||
|
|
||||||
# Built-in types might not have __qualname__ or __name__.
|
|
||||||
coro_name = getattr(
|
|
||||||
coro, '__qualname__',
|
|
||||||
getattr(coro, '__name__', type(coro).__name__))
|
|
||||||
coro_name = '{}()'.format(coro_name)
|
|
||||||
|
|
||||||
running = False
|
|
||||||
try:
|
|
||||||
running = coro.cr_running
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
running = coro.gi_running
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if running:
|
|
||||||
return '{} running'.format(coro_name)
|
|
||||||
else:
|
|
||||||
return coro_name
|
|
||||||
|
|
||||||
coro_name = None
|
|
||||||
if isinstance(coro, CoroWrapper):
|
|
||||||
func = coro.func
|
|
||||||
coro_name = coro.__qualname__
|
|
||||||
if coro_name is not None:
|
|
||||||
coro_name = '{}()'.format(coro_name)
|
|
||||||
else:
|
|
||||||
func = coro
|
|
||||||
|
|
||||||
if coro_name is None:
|
|
||||||
coro_name = events._format_callback(func, (), {})
|
|
||||||
|
|
||||||
try:
|
|
||||||
coro_code = coro.gi_code
|
|
||||||
except AttributeError:
|
|
||||||
coro_code = coro.cr_code
|
|
||||||
|
|
||||||
try:
|
|
||||||
coro_frame = coro.gi_frame
|
|
||||||
except AttributeError:
|
|
||||||
coro_frame = coro.cr_frame
|
|
||||||
|
|
||||||
filename = coro_code.co_filename
|
|
||||||
lineno = 0
|
|
||||||
if (isinstance(coro, CoroWrapper) and
|
|
||||||
not inspect.isgeneratorfunction(coro.func) and
|
|
||||||
coro.func is not None):
|
|
||||||
source = events._get_function_source(coro.func)
|
|
||||||
if source is not None:
|
|
||||||
filename, lineno = source
|
|
||||||
if coro_frame is None:
|
|
||||||
coro_repr = ('%s done, defined at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
else:
|
|
||||||
coro_repr = ('%s running, defined at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
elif coro_frame is not None:
|
|
||||||
lineno = coro_frame.f_lineno
|
|
||||||
coro_repr = ('%s running at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
else:
|
|
||||||
lineno = coro_code.co_firstlineno
|
|
||||||
coro_repr = ('%s done, defined at %s:%s'
|
|
||||||
% (coro_name, filename, lineno))
|
|
||||||
|
|
||||||
return coro_repr
|
|
||||||
@@ -1,705 +0,0 @@
|
|||||||
"""Event loop and event loop policy."""
|
|
||||||
|
|
||||||
__all__ = ['AbstractEventLoopPolicy',
|
|
||||||
'AbstractEventLoop', 'AbstractServer',
|
|
||||||
'Handle', 'TimerHandle',
|
|
||||||
'get_event_loop_policy', 'set_event_loop_policy',
|
|
||||||
'get_event_loop', 'set_event_loop', 'new_event_loop',
|
|
||||||
'get_child_watcher', 'set_child_watcher',
|
|
||||||
'_set_running_loop', 'get_running_loop',
|
|
||||||
'_get_running_loop',
|
|
||||||
]
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
import reprlib
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from asyncio import compat
|
|
||||||
|
|
||||||
|
|
||||||
def _get_function_source(func):
|
|
||||||
if compat.PY34:
|
|
||||||
func = inspect.unwrap(func)
|
|
||||||
elif hasattr(func, '__wrapped__'):
|
|
||||||
func = func.__wrapped__
|
|
||||||
if inspect.isfunction(func):
|
|
||||||
code = func.__code__
|
|
||||||
return (code.co_filename, code.co_firstlineno)
|
|
||||||
if isinstance(func, functools.partial):
|
|
||||||
return _get_function_source(func.func)
|
|
||||||
if compat.PY34 and isinstance(func, functools.partialmethod):
|
|
||||||
return _get_function_source(func.func)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _format_args_and_kwargs(args, kwargs):
|
|
||||||
"""Format function arguments and keyword arguments.
|
|
||||||
|
|
||||||
Special case for a single parameter: ('hello',) is formatted as ('hello').
|
|
||||||
"""
|
|
||||||
# use reprlib to limit the length of the output
|
|
||||||
items = []
|
|
||||||
if args:
|
|
||||||
items.extend(reprlib.repr(arg) for arg in args)
|
|
||||||
if kwargs:
|
|
||||||
items.extend('{}={}'.format(k, reprlib.repr(v))
|
|
||||||
for k, v in kwargs.items())
|
|
||||||
return '(' + ', '.join(items) + ')'
|
|
||||||
|
|
||||||
|
|
||||||
def _format_callback(func, args, kwargs, suffix=''):
|
|
||||||
if isinstance(func, functools.partial):
|
|
||||||
suffix = _format_args_and_kwargs(args, kwargs) + suffix
|
|
||||||
return _format_callback(func.func, func.args, func.keywords, suffix)
|
|
||||||
|
|
||||||
if hasattr(func, '__qualname__'):
|
|
||||||
func_repr = getattr(func, '__qualname__')
|
|
||||||
elif hasattr(func, '__name__'):
|
|
||||||
func_repr = getattr(func, '__name__')
|
|
||||||
else:
|
|
||||||
func_repr = repr(func)
|
|
||||||
|
|
||||||
func_repr += _format_args_and_kwargs(args, kwargs)
|
|
||||||
if suffix:
|
|
||||||
func_repr += suffix
|
|
||||||
return func_repr
|
|
||||||
|
|
||||||
def _format_callback_source(func, args):
|
|
||||||
func_repr = _format_callback(func, args, None)
|
|
||||||
source = _get_function_source(func)
|
|
||||||
if source:
|
|
||||||
func_repr += ' at %s:%s' % source
|
|
||||||
return func_repr
|
|
||||||
|
|
||||||
|
|
||||||
class Handle:
|
|
||||||
"""Object returned by callback registration methods."""
|
|
||||||
|
|
||||||
__slots__ = ('_callback', '_args', '_cancelled', '_loop',
|
|
||||||
'_source_traceback', '_repr', '__weakref__')
|
|
||||||
|
|
||||||
def __init__(self, callback, args, loop):
|
|
||||||
self._loop = loop
|
|
||||||
self._callback = callback
|
|
||||||
self._args = args
|
|
||||||
self._cancelled = False
|
|
||||||
self._repr = None
|
|
||||||
if self._loop.get_debug():
|
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
|
||||||
else:
|
|
||||||
self._source_traceback = None
|
|
||||||
|
|
||||||
def _repr_info(self):
|
|
||||||
info = [self.__class__.__name__]
|
|
||||||
if self._cancelled:
|
|
||||||
info.append('cancelled')
|
|
||||||
if self._callback is not None:
|
|
||||||
info.append(_format_callback_source(self._callback, self._args))
|
|
||||||
if self._source_traceback:
|
|
||||||
frame = self._source_traceback[-1]
|
|
||||||
info.append('created at %s:%s' % (frame[0], frame[1]))
|
|
||||||
return info
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self._repr is not None:
|
|
||||||
return self._repr
|
|
||||||
info = self._repr_info()
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if not self._cancelled:
|
|
||||||
self._cancelled = True
|
|
||||||
if self._loop.get_debug():
|
|
||||||
# Keep a representation in debug mode to keep callback and
|
|
||||||
# parameters. For example, to log the warning
|
|
||||||
# "Executing <Handle...> took 2.5 second"
|
|
||||||
self._repr = repr(self)
|
|
||||||
self._callback = None
|
|
||||||
self._args = None
|
|
||||||
|
|
||||||
def _run(self):
|
|
||||||
try:
|
|
||||||
self._callback(*self._args)
|
|
||||||
except Exception as exc:
|
|
||||||
cb = _format_callback_source(self._callback, self._args)
|
|
||||||
msg = 'Exception in callback {}'.format(cb)
|
|
||||||
context = {
|
|
||||||
'message': msg,
|
|
||||||
'exception': exc,
|
|
||||||
'handle': self,
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
self = None # Needed to break cycles when an exception occurs.
|
|
||||||
|
|
||||||
|
|
||||||
class TimerHandle(Handle):
|
|
||||||
"""Object returned by timed callback registration methods."""
|
|
||||||
|
|
||||||
__slots__ = ['_scheduled', '_when']
|
|
||||||
|
|
||||||
def __init__(self, when, callback, args, loop):
|
|
||||||
assert when is not None
|
|
||||||
super().__init__(callback, args, loop)
|
|
||||||
if self._source_traceback:
|
|
||||||
del self._source_traceback[-1]
|
|
||||||
self._when = when
|
|
||||||
self._scheduled = False
|
|
||||||
|
|
||||||
def _repr_info(self):
|
|
||||||
info = super()._repr_info()
|
|
||||||
pos = 2 if self._cancelled else 1
|
|
||||||
info.insert(pos, 'when=%s' % self._when)
|
|
||||||
return info
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self._when)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return self._when < other._when
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
if self._when < other._when:
|
|
||||||
return True
|
|
||||||
return self.__eq__(other)
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self._when > other._when
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
if self._when > other._when:
|
|
||||||
return True
|
|
||||||
return self.__eq__(other)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, TimerHandle):
|
|
||||||
return (self._when == other._when and
|
|
||||||
self._callback == other._callback and
|
|
||||||
self._args == other._args and
|
|
||||||
self._cancelled == other._cancelled)
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
equal = self.__eq__(other)
|
|
||||||
return NotImplemented if equal is NotImplemented else not equal
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if not self._cancelled:
|
|
||||||
self._loop._timer_handle_cancelled(self)
|
|
||||||
super().cancel()
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractServer:
|
|
||||||
"""Abstract server returned by create_server()."""
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Stop serving. This leaves existing connections open."""
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def wait_closed(self):
|
|
||||||
"""Coroutine to wait until service is closed."""
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractEventLoop:
|
|
||||||
"""Abstract event loop."""
|
|
||||||
|
|
||||||
# Running and stopping the event loop.
|
|
||||||
|
|
||||||
def run_forever(self):
|
|
||||||
"""Run the event loop until stop() is called."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def run_until_complete(self, future):
|
|
||||||
"""Run the event loop until a Future is done.
|
|
||||||
|
|
||||||
Return the Future's result, or raise its exception.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stop the event loop as soon as reasonable.
|
|
||||||
|
|
||||||
Exactly how soon that is may depend on the implementation, but
|
|
||||||
no more I/O callbacks should be scheduled.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def is_running(self):
|
|
||||||
"""Return whether the event loop is currently running."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def is_closed(self):
|
|
||||||
"""Returns True if the event loop was closed."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the loop.
|
|
||||||
|
|
||||||
The loop should not be running.
|
|
||||||
|
|
||||||
This is idempotent and irreversible.
|
|
||||||
|
|
||||||
No other methods should be called after this one.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def shutdown_asyncgens(self):
|
|
||||||
"""Shutdown all active asynchronous generators."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Methods scheduling callbacks. All these return Handles.
|
|
||||||
|
|
||||||
def _timer_handle_cancelled(self, handle):
|
|
||||||
"""Notification that a TimerHandle has been cancelled."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def call_soon(self, callback, *args):
|
|
||||||
return self.call_later(0, callback, *args)
|
|
||||||
|
|
||||||
def call_later(self, delay, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def call_at(self, when, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def time(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_future(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Method scheduling a coroutine object: create a task.
|
|
||||||
|
|
||||||
def create_task(self, coro):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Methods for interacting with threads.
|
|
||||||
|
|
||||||
def call_soon_threadsafe(self, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def run_in_executor(self, executor, func, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_default_executor(self, executor):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Network I/O methods returning Futures.
|
|
||||||
|
|
||||||
def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def getnameinfo(self, sockaddr, flags=0):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_connection(self, protocol_factory, host=None, port=None, *,
|
|
||||||
ssl=None, family=0, proto=0, flags=0, sock=None,
|
|
||||||
local_addr=None, server_hostname=None):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_server(self, protocol_factory, host=None, port=None, *,
|
|
||||||
family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE,
|
|
||||||
sock=None, backlog=100, ssl=None, reuse_address=None,
|
|
||||||
reuse_port=None):
|
|
||||||
"""A coroutine which creates a TCP server bound to host and port.
|
|
||||||
|
|
||||||
The return value is a Server object which can be used to stop
|
|
||||||
the service.
|
|
||||||
|
|
||||||
If host is an empty string or None all interfaces are assumed
|
|
||||||
and a list of multiple sockets will be returned (most likely
|
|
||||||
one for IPv4 and another one for IPv6). The host parameter can also be a
|
|
||||||
sequence (e.g. list) of hosts to bind to.
|
|
||||||
|
|
||||||
family can be set to either AF_INET or AF_INET6 to force the
|
|
||||||
socket to use IPv4 or IPv6. If not set it will be determined
|
|
||||||
from host (defaults to AF_UNSPEC).
|
|
||||||
|
|
||||||
flags is a bitmask for getaddrinfo().
|
|
||||||
|
|
||||||
sock can optionally be specified in order to use a preexisting
|
|
||||||
socket object.
|
|
||||||
|
|
||||||
backlog is the maximum number of queued connections passed to
|
|
||||||
listen() (defaults to 100).
|
|
||||||
|
|
||||||
ssl can be set to an SSLContext to enable SSL over the
|
|
||||||
accepted connections.
|
|
||||||
|
|
||||||
reuse_address tells the kernel to reuse a local socket in
|
|
||||||
TIME_WAIT state, without waiting for its natural timeout to
|
|
||||||
expire. If not specified will automatically be set to True on
|
|
||||||
UNIX.
|
|
||||||
|
|
||||||
reuse_port tells the kernel to allow this endpoint to be bound to
|
|
||||||
the same port as other existing endpoints are bound to, so long as
|
|
||||||
they all set this flag when being created. This option is not
|
|
||||||
supported on Windows.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_unix_connection(self, protocol_factory, path, *,
|
|
||||||
ssl=None, sock=None,
|
|
||||||
server_hostname=None):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_unix_server(self, protocol_factory, path, *,
|
|
||||||
sock=None, backlog=100, ssl=None):
|
|
||||||
"""A coroutine which creates a UNIX Domain Socket server.
|
|
||||||
|
|
||||||
The return value is a Server object, which can be used to stop
|
|
||||||
the service.
|
|
||||||
|
|
||||||
path is a str, representing a file systsem path to bind the
|
|
||||||
server socket to.
|
|
||||||
|
|
||||||
sock can optionally be specified in order to use a preexisting
|
|
||||||
socket object.
|
|
||||||
|
|
||||||
backlog is the maximum number of queued connections passed to
|
|
||||||
listen() (defaults to 100).
|
|
||||||
|
|
||||||
ssl can be set to an SSLContext to enable SSL over the
|
|
||||||
accepted connections.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_datagram_endpoint(self, protocol_factory,
|
|
||||||
local_addr=None, remote_addr=None, *,
|
|
||||||
family=0, proto=0, flags=0,
|
|
||||||
reuse_address=None, reuse_port=None,
|
|
||||||
allow_broadcast=None, sock=None):
|
|
||||||
"""A coroutine which creates a datagram endpoint.
|
|
||||||
|
|
||||||
This method will try to establish the endpoint in the background.
|
|
||||||
When successful, the coroutine returns a (transport, protocol) pair.
|
|
||||||
|
|
||||||
protocol_factory must be a callable returning a protocol instance.
|
|
||||||
|
|
||||||
socket family AF_INET or socket.AF_INET6 depending on host (or
|
|
||||||
family if specified), socket type SOCK_DGRAM.
|
|
||||||
|
|
||||||
reuse_address tells the kernel to reuse a local socket in
|
|
||||||
TIME_WAIT state, without waiting for its natural timeout to
|
|
||||||
expire. If not specified it will automatically be set to True on
|
|
||||||
UNIX.
|
|
||||||
|
|
||||||
reuse_port tells the kernel to allow this endpoint to be bound to
|
|
||||||
the same port as other existing endpoints are bound to, so long as
|
|
||||||
they all set this flag when being created. This option is not
|
|
||||||
supported on Windows and some UNIX's. If the
|
|
||||||
:py:data:`~socket.SO_REUSEPORT` constant is not defined then this
|
|
||||||
capability is unsupported.
|
|
||||||
|
|
||||||
allow_broadcast tells the kernel to allow this endpoint to send
|
|
||||||
messages to the broadcast address.
|
|
||||||
|
|
||||||
sock can optionally be specified in order to use a preexisting
|
|
||||||
socket object.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Pipes and subprocesses.
|
|
||||||
|
|
||||||
def connect_read_pipe(self, protocol_factory, pipe):
|
|
||||||
"""Register read pipe in event loop. Set the pipe to non-blocking mode.
|
|
||||||
|
|
||||||
protocol_factory should instantiate object with Protocol interface.
|
|
||||||
pipe is a file-like object.
|
|
||||||
Return pair (transport, protocol), where transport supports the
|
|
||||||
ReadTransport interface."""
|
|
||||||
# The reason to accept file-like object instead of just file descriptor
|
|
||||||
# is: we need to own pipe and close it at transport finishing
|
|
||||||
# Can got complicated errors if pass f.fileno(),
|
|
||||||
# close fd in pipe transport then close f and vise versa.
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def connect_write_pipe(self, protocol_factory, pipe):
|
|
||||||
"""Register write pipe in event loop.
|
|
||||||
|
|
||||||
protocol_factory should instantiate object with BaseProtocol interface.
|
|
||||||
Pipe is file-like object already switched to nonblocking.
|
|
||||||
Return pair (transport, protocol), where transport support
|
|
||||||
WriteTransport interface."""
|
|
||||||
# The reason to accept file-like object instead of just file descriptor
|
|
||||||
# is: we need to own pipe and close it at transport finishing
|
|
||||||
# Can got complicated errors if pass f.fileno(),
|
|
||||||
# close fd in pipe transport then close f and vise versa.
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
||||||
**kwargs):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
||||||
**kwargs):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Ready-based callback registration methods.
|
|
||||||
# The add_*() methods return None.
|
|
||||||
# The remove_*() methods return True if something was removed,
|
|
||||||
# False if there was nothing to delete.
|
|
||||||
|
|
||||||
def add_reader(self, fd, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def remove_reader(self, fd):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def add_writer(self, fd, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def remove_writer(self, fd):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Completion based I/O methods returning Futures.
|
|
||||||
|
|
||||||
def sock_recv(self, sock, nbytes):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def sock_sendall(self, sock, data):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def sock_connect(self, sock, address):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def sock_accept(self, sock):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Signal handling.
|
|
||||||
|
|
||||||
def add_signal_handler(self, sig, callback, *args):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def remove_signal_handler(self, sig):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Task factory.
|
|
||||||
|
|
||||||
def set_task_factory(self, factory):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_task_factory(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Error handlers.
|
|
||||||
|
|
||||||
def get_exception_handler(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_exception_handler(self, handler):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def default_exception_handler(self, context):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def call_exception_handler(self, context):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Debug flag management.
|
|
||||||
|
|
||||||
def get_debug(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_debug(self, enabled):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractEventLoopPolicy:
|
|
||||||
"""Abstract policy for accessing the event loop."""
|
|
||||||
|
|
||||||
def get_event_loop(self):
|
|
||||||
"""Get the event loop for the current context.
|
|
||||||
|
|
||||||
Returns an event loop object implementing the BaseEventLoop interface,
|
|
||||||
or raises an exception in case no event loop has been set for the
|
|
||||||
current context and the current policy does not specify to create one.
|
|
||||||
|
|
||||||
It should never return None."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_event_loop(self, loop):
|
|
||||||
"""Set the event loop for the current context to loop."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def new_event_loop(self):
|
|
||||||
"""Create and return a new event loop object according to this
|
|
||||||
policy's rules. If there's need to set this loop as the event loop for
|
|
||||||
the current context, set_event_loop must be called explicitly."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# Child processes handling (Unix only).
|
|
||||||
|
|
||||||
def get_child_watcher(self):
|
|
||||||
"Get the watcher for child processes."
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_child_watcher(self, watcher):
|
|
||||||
"""Set the watcher for child processes."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
|
|
||||||
"""Default policy implementation for accessing the event loop.
|
|
||||||
|
|
||||||
In this policy, each thread has its own event loop. However, we
|
|
||||||
only automatically create an event loop by default for the main
|
|
||||||
thread; other threads by default have no event loop.
|
|
||||||
|
|
||||||
Other policies may have different rules (e.g. a single global
|
|
||||||
event loop, or automatically creating an event loop per thread, or
|
|
||||||
using some other notion of context to which an event loop is
|
|
||||||
associated).
|
|
||||||
"""
|
|
||||||
|
|
||||||
_loop_factory = None
|
|
||||||
|
|
||||||
class _Local(threading.local):
|
|
||||||
_loop = None
|
|
||||||
_set_called = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._local = self._Local()
|
|
||||||
|
|
||||||
def get_event_loop(self):
|
|
||||||
"""Get the event loop.
|
|
||||||
|
|
||||||
This may be None or an instance of EventLoop.
|
|
||||||
"""
|
|
||||||
if (self._local._loop is None and
|
|
||||||
not self._local._set_called and
|
|
||||||
isinstance(threading.current_thread(), threading._MainThread)):
|
|
||||||
self.set_event_loop(self.new_event_loop())
|
|
||||||
if self._local._loop is None:
|
|
||||||
raise RuntimeError('There is no current event loop in thread %r.'
|
|
||||||
% threading.current_thread().name)
|
|
||||||
return self._local._loop
|
|
||||||
|
|
||||||
def set_event_loop(self, loop):
|
|
||||||
"""Set the event loop."""
|
|
||||||
self._local._set_called = True
|
|
||||||
assert loop is None or isinstance(loop, AbstractEventLoop)
|
|
||||||
self._local._loop = loop
|
|
||||||
|
|
||||||
def new_event_loop(self):
|
|
||||||
"""Create a new event loop.
|
|
||||||
|
|
||||||
You must call set_event_loop() to make this the current event
|
|
||||||
loop.
|
|
||||||
"""
|
|
||||||
return self._loop_factory()
|
|
||||||
|
|
||||||
|
|
||||||
# Event loop policy. The policy itself is always global, even if the
|
|
||||||
# policy's rules say that there is an event loop per thread (or other
|
|
||||||
# notion of context). The default policy is installed by the first
|
|
||||||
# call to get_event_loop_policy().
|
|
||||||
_event_loop_policy = None
|
|
||||||
|
|
||||||
# Lock for protecting the on-the-fly creation of the event loop policy.
|
|
||||||
_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
# A TLS for the running event loop, used by _get_running_loop.
|
|
||||||
class _RunningLoop(threading.local):
|
|
||||||
_loop = None
|
|
||||||
_running_loop = _RunningLoop()
|
|
||||||
|
|
||||||
|
|
||||||
def get_running_loop():
|
|
||||||
"""Return the running event loop. Raise a RuntimeError if there is none.
|
|
||||||
|
|
||||||
This function is thread-specific.
|
|
||||||
"""
|
|
||||||
# NOTE: this function is implemented in C (see _asynciomodule.c)
|
|
||||||
loop = _get_running_loop()
|
|
||||||
if loop is None:
|
|
||||||
raise RuntimeError('no running event loop')
|
|
||||||
return loop
|
|
||||||
|
|
||||||
|
|
||||||
def _get_running_loop():
|
|
||||||
"""Return the running event loop or None.
|
|
||||||
|
|
||||||
This is a low-level function intended to be used by event loops.
|
|
||||||
This function is thread-specific.
|
|
||||||
"""
|
|
||||||
return _running_loop._loop
|
|
||||||
|
|
||||||
|
|
||||||
def _set_running_loop(loop):
|
|
||||||
"""Set the running event loop.
|
|
||||||
|
|
||||||
This is a low-level function intended to be used by event loops.
|
|
||||||
This function is thread-specific.
|
|
||||||
"""
|
|
||||||
_running_loop._loop = loop
|
|
||||||
|
|
||||||
|
|
||||||
def _init_event_loop_policy():
|
|
||||||
global _event_loop_policy
|
|
||||||
with _lock:
|
|
||||||
if _event_loop_policy is None: # pragma: no branch
|
|
||||||
from . import DefaultEventLoopPolicy
|
|
||||||
_event_loop_policy = DefaultEventLoopPolicy()
|
|
||||||
|
|
||||||
|
|
||||||
def get_event_loop_policy():
|
|
||||||
"""Get the current event loop policy."""
|
|
||||||
if _event_loop_policy is None:
|
|
||||||
_init_event_loop_policy()
|
|
||||||
return _event_loop_policy
|
|
||||||
|
|
||||||
|
|
||||||
def set_event_loop_policy(policy):
|
|
||||||
"""Set the current event loop policy.
|
|
||||||
|
|
||||||
If policy is None, the default policy is restored."""
|
|
||||||
global _event_loop_policy
|
|
||||||
assert policy is None or isinstance(policy, AbstractEventLoopPolicy)
|
|
||||||
_event_loop_policy = policy
|
|
||||||
|
|
||||||
|
|
||||||
def get_event_loop():
|
|
||||||
"""Return an asyncio event loop.
|
|
||||||
|
|
||||||
When called from a coroutine or a callback (e.g. scheduled with call_soon
|
|
||||||
or similar API), this function will always return the running event loop.
|
|
||||||
|
|
||||||
If there is no running event loop set, the function will return
|
|
||||||
the result of `get_event_loop_policy().get_event_loop()` call.
|
|
||||||
"""
|
|
||||||
current_loop = _get_running_loop()
|
|
||||||
if current_loop is not None:
|
|
||||||
return current_loop
|
|
||||||
return get_event_loop_policy().get_event_loop()
|
|
||||||
|
|
||||||
|
|
||||||
def set_event_loop(loop):
|
|
||||||
"""Equivalent to calling get_event_loop_policy().set_event_loop(loop)."""
|
|
||||||
get_event_loop_policy().set_event_loop(loop)
|
|
||||||
|
|
||||||
|
|
||||||
def new_event_loop():
|
|
||||||
"""Equivalent to calling get_event_loop_policy().new_event_loop()."""
|
|
||||||
return get_event_loop_policy().new_event_loop()
|
|
||||||
|
|
||||||
|
|
||||||
def get_child_watcher():
|
|
||||||
"""Equivalent to calling get_event_loop_policy().get_child_watcher()."""
|
|
||||||
return get_event_loop_policy().get_child_watcher()
|
|
||||||
|
|
||||||
|
|
||||||
def set_child_watcher(watcher):
|
|
||||||
"""Equivalent to calling
|
|
||||||
get_event_loop_policy().set_child_watcher(watcher)."""
|
|
||||||
return get_event_loop_policy().set_child_watcher(watcher)
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
"""A Future class similar to the one in PEP 3148."""
|
|
||||||
|
|
||||||
__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError',
|
|
||||||
'Future', 'wrap_future', 'isfuture']
|
|
||||||
|
|
||||||
import concurrent.futures
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from . import base_futures
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
|
|
||||||
|
|
||||||
CancelledError = base_futures.CancelledError
|
|
||||||
InvalidStateError = base_futures.InvalidStateError
|
|
||||||
TimeoutError = base_futures.TimeoutError
|
|
||||||
isfuture = base_futures.isfuture
|
|
||||||
|
|
||||||
|
|
||||||
_PENDING = base_futures._PENDING
|
|
||||||
_CANCELLED = base_futures._CANCELLED
|
|
||||||
_FINISHED = base_futures._FINISHED
|
|
||||||
|
|
||||||
|
|
||||||
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
|
|
||||||
|
|
||||||
|
|
||||||
class _TracebackLogger:
|
|
||||||
"""Helper to log a traceback upon destruction if not cleared.
|
|
||||||
|
|
||||||
This solves a nasty problem with Futures and Tasks that have an
|
|
||||||
exception set: if nobody asks for the exception, the exception is
|
|
||||||
never logged. This violates the Zen of Python: 'Errors should
|
|
||||||
never pass silently. Unless explicitly silenced.'
|
|
||||||
|
|
||||||
However, we don't want to log the exception as soon as
|
|
||||||
set_exception() is called: if the calling code is written
|
|
||||||
properly, it will get the exception and handle it properly. But
|
|
||||||
we *do* want to log it if result() or exception() was never called
|
|
||||||
-- otherwise developers waste a lot of time wondering why their
|
|
||||||
buggy code fails silently.
|
|
||||||
|
|
||||||
An earlier attempt added a __del__() method to the Future class
|
|
||||||
itself, but this backfired because the presence of __del__()
|
|
||||||
prevents garbage collection from breaking cycles. A way out of
|
|
||||||
this catch-22 is to avoid having a __del__() method on the Future
|
|
||||||
class itself, but instead to have a reference to a helper object
|
|
||||||
with a __del__() method that logs the traceback, where we ensure
|
|
||||||
that the helper object doesn't participate in cycles, and only the
|
|
||||||
Future has a reference to it.
|
|
||||||
|
|
||||||
The helper object is added when set_exception() is called. When
|
|
||||||
the Future is collected, and the helper is present, the helper
|
|
||||||
object is also collected, and its __del__() method will log the
|
|
||||||
traceback. When the Future's result() or exception() method is
|
|
||||||
called (and a helper object is present), it removes the helper
|
|
||||||
object, after calling its clear() method to prevent it from
|
|
||||||
logging.
|
|
||||||
|
|
||||||
One downside is that we do a fair amount of work to extract the
|
|
||||||
traceback from the exception, even when it is never logged. It
|
|
||||||
would seem cheaper to just store the exception object, but that
|
|
||||||
references the traceback, which references stack frames, which may
|
|
||||||
reference the Future, which references the _TracebackLogger, and
|
|
||||||
then the _TracebackLogger would be included in a cycle, which is
|
|
||||||
what we're trying to avoid! As an optimization, we don't
|
|
||||||
immediately format the exception; we only do the work when
|
|
||||||
activate() is called, which call is delayed until after all the
|
|
||||||
Future's callbacks have run. Since usually a Future has at least
|
|
||||||
one callback (typically set by 'yield from') and usually that
|
|
||||||
callback extracts the callback, thereby removing the need to
|
|
||||||
format the exception.
|
|
||||||
|
|
||||||
PS. I don't claim credit for this solution. I first heard of it
|
|
||||||
in a discussion about closing files when they are collected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ('loop', 'source_traceback', 'exc', 'tb')
|
|
||||||
|
|
||||||
def __init__(self, future, exc):
|
|
||||||
self.loop = future._loop
|
|
||||||
self.source_traceback = future._source_traceback
|
|
||||||
self.exc = exc
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
exc = self.exc
|
|
||||||
if exc is not None:
|
|
||||||
self.exc = None
|
|
||||||
self.tb = traceback.format_exception(exc.__class__, exc,
|
|
||||||
exc.__traceback__)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.exc = None
|
|
||||||
self.tb = None
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self.tb:
|
|
||||||
msg = 'Future/Task exception was never retrieved\n'
|
|
||||||
if self.source_traceback:
|
|
||||||
src = ''.join(traceback.format_list(self.source_traceback))
|
|
||||||
msg += 'Future/Task created at (most recent call last):\n'
|
|
||||||
msg += '%s\n' % src.rstrip()
|
|
||||||
msg += ''.join(self.tb).rstrip()
|
|
||||||
self.loop.call_exception_handler({'message': msg})
|
|
||||||
|
|
||||||
|
|
||||||
class Future:
|
|
||||||
"""This class is *almost* compatible with concurrent.futures.Future.
|
|
||||||
|
|
||||||
Differences:
|
|
||||||
|
|
||||||
- result() and exception() do not take a timeout argument and
|
|
||||||
raise an exception when the future isn't done yet.
|
|
||||||
|
|
||||||
- Callbacks registered with add_done_callback() are always called
|
|
||||||
via the event loop's call_soon_threadsafe().
|
|
||||||
|
|
||||||
- This class is not compatible with the wait() and as_completed()
|
|
||||||
methods in the concurrent.futures package.
|
|
||||||
|
|
||||||
(In Python 3.4 or later we may be able to unify the implementations.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Class variables serving as defaults for instance variables.
|
|
||||||
_state = _PENDING
|
|
||||||
_result = None
|
|
||||||
_exception = None
|
|
||||||
_loop = None
|
|
||||||
_source_traceback = None
|
|
||||||
|
|
||||||
# This field is used for a dual purpose:
|
|
||||||
# - Its presence is a marker to declare that a class implements
|
|
||||||
# the Future protocol (i.e. is intended to be duck-type compatible).
|
|
||||||
# The value must also be not-None, to enable a subclass to declare
|
|
||||||
# that it is not compatible by setting this to None.
|
|
||||||
# - It is set by __iter__() below so that Task._step() can tell
|
|
||||||
# the difference between `yield from Future()` (correct) vs.
|
|
||||||
# `yield Future()` (incorrect).
|
|
||||||
_asyncio_future_blocking = False
|
|
||||||
|
|
||||||
_log_traceback = False # Used for Python 3.4 and later
|
|
||||||
_tb_logger = None # Used for Python 3.3 only
|
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
|
||||||
"""Initialize the future.
|
|
||||||
|
|
||||||
The optional event_loop argument allows explicitly setting the event
|
|
||||||
loop object used by the future. If it's not provided, the future uses
|
|
||||||
the default event loop.
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
else:
|
|
||||||
self._loop = loop
|
|
||||||
self._callbacks = []
|
|
||||||
if self._loop.get_debug():
|
|
||||||
self._source_traceback = traceback.extract_stack(sys._getframe(1))
|
|
||||||
|
|
||||||
_repr_info = base_futures._future_repr_info
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
|
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
|
||||||
if not self._log_traceback:
|
|
||||||
# set_exception() was not called, or result() or exception()
|
|
||||||
# has consumed the exception
|
|
||||||
return
|
|
||||||
exc = self._exception
|
|
||||||
context = {
|
|
||||||
'message': ('%s exception was never retrieved'
|
|
||||||
% self.__class__.__name__),
|
|
||||||
'exception': exc,
|
|
||||||
'future': self,
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
"""Cancel the future and schedule callbacks.
|
|
||||||
|
|
||||||
If the future is already done or cancelled, return False. Otherwise,
|
|
||||||
change the future's state to cancelled, schedule the callbacks and
|
|
||||||
return True.
|
|
||||||
"""
|
|
||||||
if self._state != _PENDING:
|
|
||||||
return False
|
|
||||||
self._state = _CANCELLED
|
|
||||||
self._schedule_callbacks()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _schedule_callbacks(self):
|
|
||||||
"""Internal: Ask the event loop to call all callbacks.
|
|
||||||
|
|
||||||
The callbacks are scheduled to be called as soon as possible. Also
|
|
||||||
clears the callback list.
|
|
||||||
"""
|
|
||||||
callbacks = self._callbacks[:]
|
|
||||||
if not callbacks:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._callbacks[:] = []
|
|
||||||
for callback in callbacks:
|
|
||||||
self._loop.call_soon(callback, self)
|
|
||||||
|
|
||||||
def cancelled(self):
|
|
||||||
"""Return True if the future was cancelled."""
|
|
||||||
return self._state == _CANCELLED
|
|
||||||
|
|
||||||
# Don't implement running(); see http://bugs.python.org/issue18699
|
|
||||||
|
|
||||||
def done(self):
|
|
||||||
"""Return True if the future is done.
|
|
||||||
|
|
||||||
Done means either that a result / exception are available, or that the
|
|
||||||
future was cancelled.
|
|
||||||
"""
|
|
||||||
return self._state != _PENDING
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
"""Return the result this future represents.
|
|
||||||
|
|
||||||
If the future has been cancelled, raises CancelledError. If the
|
|
||||||
future's result isn't yet available, raises InvalidStateError. If
|
|
||||||
the future is done and has an exception set, this exception is raised.
|
|
||||||
"""
|
|
||||||
if self._state == _CANCELLED:
|
|
||||||
raise CancelledError
|
|
||||||
if self._state != _FINISHED:
|
|
||||||
raise InvalidStateError('Result is not ready.')
|
|
||||||
self._log_traceback = False
|
|
||||||
if self._tb_logger is not None:
|
|
||||||
self._tb_logger.clear()
|
|
||||||
self._tb_logger = None
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
def exception(self):
|
|
||||||
"""Return the exception that was set on this future.
|
|
||||||
|
|
||||||
The exception (or None if no exception was set) is returned only if
|
|
||||||
the future is done. If the future has been cancelled, raises
|
|
||||||
CancelledError. If the future isn't done yet, raises
|
|
||||||
InvalidStateError.
|
|
||||||
"""
|
|
||||||
if self._state == _CANCELLED:
|
|
||||||
raise CancelledError
|
|
||||||
if self._state != _FINISHED:
|
|
||||||
raise InvalidStateError('Exception is not set.')
|
|
||||||
self._log_traceback = False
|
|
||||||
if self._tb_logger is not None:
|
|
||||||
self._tb_logger.clear()
|
|
||||||
self._tb_logger = None
|
|
||||||
return self._exception
|
|
||||||
|
|
||||||
def add_done_callback(self, fn):
|
|
||||||
"""Add a callback to be run when the future becomes done.
|
|
||||||
|
|
||||||
The callback is called with a single argument - the future object. If
|
|
||||||
the future is already done when this is called, the callback is
|
|
||||||
scheduled with call_soon.
|
|
||||||
"""
|
|
||||||
if self._state != _PENDING:
|
|
||||||
self._loop.call_soon(fn, self)
|
|
||||||
else:
|
|
||||||
self._callbacks.append(fn)
|
|
||||||
|
|
||||||
# New method not in PEP 3148.
|
|
||||||
|
|
||||||
def remove_done_callback(self, fn):
|
|
||||||
"""Remove all instances of a callback from the "call when done" list.
|
|
||||||
|
|
||||||
Returns the number of callbacks removed.
|
|
||||||
"""
|
|
||||||
filtered_callbacks = [f for f in self._callbacks if f != fn]
|
|
||||||
removed_count = len(self._callbacks) - len(filtered_callbacks)
|
|
||||||
if removed_count:
|
|
||||||
self._callbacks[:] = filtered_callbacks
|
|
||||||
return removed_count
|
|
||||||
|
|
||||||
# So-called internal methods (note: no set_running_or_notify_cancel()).
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
"""Mark the future done and set its result.
|
|
||||||
|
|
||||||
If the future is already done when this method is called, raises
|
|
||||||
InvalidStateError.
|
|
||||||
"""
|
|
||||||
if self._state != _PENDING:
|
|
||||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
|
||||||
self._result = result
|
|
||||||
self._state = _FINISHED
|
|
||||||
self._schedule_callbacks()
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
"""Mark the future done and set an exception.
|
|
||||||
|
|
||||||
If the future is already done when this method is called, raises
|
|
||||||
InvalidStateError.
|
|
||||||
"""
|
|
||||||
if self._state != _PENDING:
|
|
||||||
raise InvalidStateError('{}: {!r}'.format(self._state, self))
|
|
||||||
if isinstance(exception, type):
|
|
||||||
exception = exception()
|
|
||||||
if type(exception) is StopIteration:
|
|
||||||
raise TypeError("StopIteration interacts badly with generators "
|
|
||||||
"and cannot be raised into a Future")
|
|
||||||
self._exception = exception
|
|
||||||
self._state = _FINISHED
|
|
||||||
self._schedule_callbacks()
|
|
||||||
if compat.PY34:
|
|
||||||
self._log_traceback = True
|
|
||||||
else:
|
|
||||||
self._tb_logger = _TracebackLogger(self, exception)
|
|
||||||
# Arrange for the logger to be activated after all callbacks
|
|
||||||
# have had a chance to call result() or exception().
|
|
||||||
self._loop.call_soon(self._tb_logger.activate)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if not self.done():
|
|
||||||
self._asyncio_future_blocking = True
|
|
||||||
yield self # This tells Task to wait for completion.
|
|
||||||
assert self.done(), "yield from wasn't used with future"
|
|
||||||
return self.result() # May raise too.
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
__await__ = __iter__ # make compatible with 'await' expression
|
|
||||||
|
|
||||||
|
|
||||||
# Needed for testing purposes.
|
|
||||||
_PyFuture = Future
|
|
||||||
|
|
||||||
|
|
||||||
def _set_result_unless_cancelled(fut, result):
|
|
||||||
"""Helper setting the result only if the future was not cancelled."""
|
|
||||||
if fut.cancelled():
|
|
||||||
return
|
|
||||||
fut.set_result(result)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_concurrent_future_state(concurrent, source):
|
|
||||||
"""Copy state from a future to a concurrent.futures.Future."""
|
|
||||||
assert source.done()
|
|
||||||
if source.cancelled():
|
|
||||||
concurrent.cancel()
|
|
||||||
if not concurrent.set_running_or_notify_cancel():
|
|
||||||
return
|
|
||||||
exception = source.exception()
|
|
||||||
if exception is not None:
|
|
||||||
concurrent.set_exception(exception)
|
|
||||||
else:
|
|
||||||
result = source.result()
|
|
||||||
concurrent.set_result(result)
|
|
||||||
|
|
||||||
|
|
||||||
def _copy_future_state(source, dest):
|
|
||||||
"""Internal helper to copy state from another Future.
|
|
||||||
|
|
||||||
The other Future may be a concurrent.futures.Future.
|
|
||||||
"""
|
|
||||||
assert source.done()
|
|
||||||
if dest.cancelled():
|
|
||||||
return
|
|
||||||
assert not dest.done()
|
|
||||||
if source.cancelled():
|
|
||||||
dest.cancel()
|
|
||||||
else:
|
|
||||||
exception = source.exception()
|
|
||||||
if exception is not None:
|
|
||||||
dest.set_exception(exception)
|
|
||||||
else:
|
|
||||||
result = source.result()
|
|
||||||
dest.set_result(result)
|
|
||||||
|
|
||||||
|
|
||||||
def _chain_future(source, destination):
|
|
||||||
"""Chain two futures so that when one completes, so does the other.
|
|
||||||
|
|
||||||
The result (or exception) of source will be copied to destination.
|
|
||||||
If destination is cancelled, source gets cancelled too.
|
|
||||||
Compatible with both asyncio.Future and concurrent.futures.Future.
|
|
||||||
"""
|
|
||||||
if not isfuture(source) and not isinstance(source,
|
|
||||||
concurrent.futures.Future):
|
|
||||||
raise TypeError('A future is required for source argument')
|
|
||||||
if not isfuture(destination) and not isinstance(destination,
|
|
||||||
concurrent.futures.Future):
|
|
||||||
raise TypeError('A future is required for destination argument')
|
|
||||||
source_loop = source._loop if isfuture(source) else None
|
|
||||||
dest_loop = destination._loop if isfuture(destination) else None
|
|
||||||
|
|
||||||
def _set_state(future, other):
|
|
||||||
if isfuture(future):
|
|
||||||
_copy_future_state(other, future)
|
|
||||||
else:
|
|
||||||
_set_concurrent_future_state(future, other)
|
|
||||||
|
|
||||||
def _call_check_cancel(destination):
|
|
||||||
if destination.cancelled():
|
|
||||||
if source_loop is None or source_loop is dest_loop:
|
|
||||||
source.cancel()
|
|
||||||
else:
|
|
||||||
source_loop.call_soon_threadsafe(source.cancel)
|
|
||||||
|
|
||||||
def _call_set_state(source):
|
|
||||||
if dest_loop is None or dest_loop is source_loop:
|
|
||||||
_set_state(destination, source)
|
|
||||||
else:
|
|
||||||
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
|
||||||
|
|
||||||
destination.add_done_callback(_call_check_cancel)
|
|
||||||
source.add_done_callback(_call_set_state)
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_future(future, *, loop=None):
|
|
||||||
"""Wrap concurrent.futures.Future object."""
|
|
||||||
if isfuture(future):
|
|
||||||
return future
|
|
||||||
assert isinstance(future, concurrent.futures.Future), \
|
|
||||||
'concurrent.futures.Future is expected, got {!r}'.format(future)
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
new_future = loop.create_future()
|
|
||||||
_chain_future(future, new_future)
|
|
||||||
return new_future
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import _asyncio
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# _CFuture is needed for tests.
|
|
||||||
Future = _CFuture = _asyncio.Future
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
"""Synchronization primitives."""
|
|
||||||
|
|
||||||
__all__ = ['Lock', 'Event', 'Condition', 'Semaphore', 'BoundedSemaphore']
|
|
||||||
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import futures
|
|
||||||
from .coroutines import coroutine
|
|
||||||
|
|
||||||
|
|
||||||
class _ContextManager:
|
|
||||||
"""Context manager.
|
|
||||||
|
|
||||||
This enables the following idiom for acquiring and releasing a
|
|
||||||
lock around a block:
|
|
||||||
|
|
||||||
with (yield from lock):
|
|
||||||
<block>
|
|
||||||
|
|
||||||
while failing loudly when accidentally using:
|
|
||||||
|
|
||||||
with lock:
|
|
||||||
<block>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, lock):
|
|
||||||
self._lock = lock
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# We have no use for the "as ..." clause in the with
|
|
||||||
# statement for locks.
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
try:
|
|
||||||
self._lock.release()
|
|
||||||
finally:
|
|
||||||
self._lock = None # Crudely prevent reuse.
|
|
||||||
|
|
||||||
|
|
||||||
class _ContextManagerMixin:
|
|
||||||
def __enter__(self):
|
|
||||||
raise RuntimeError(
|
|
||||||
'"yield from" should be used as context manager expression')
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
# This must exist because __enter__ exists, even though that
|
|
||||||
# always raises; that's how the with-statement works.
|
|
||||||
pass
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __iter__(self):
|
|
||||||
# This is not a coroutine. It is meant to enable the idiom:
|
|
||||||
#
|
|
||||||
# with (yield from lock):
|
|
||||||
# <block>
|
|
||||||
#
|
|
||||||
# as an alternative to:
|
|
||||||
#
|
|
||||||
# yield from lock.acquire()
|
|
||||||
# try:
|
|
||||||
# <block>
|
|
||||||
# finally:
|
|
||||||
# lock.release()
|
|
||||||
yield from self.acquire()
|
|
||||||
return _ContextManager(self)
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
|
|
||||||
def __await__(self):
|
|
||||||
# To make "with await lock" work.
|
|
||||||
yield from self.acquire()
|
|
||||||
return _ContextManager(self)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __aenter__(self):
|
|
||||||
yield from self.acquire()
|
|
||||||
# We have no use for the "as ..." clause in the with
|
|
||||||
# statement for locks.
|
|
||||||
return None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __aexit__(self, exc_type, exc, tb):
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
|
|
||||||
class Lock(_ContextManagerMixin):
|
|
||||||
"""Primitive lock objects.
|
|
||||||
|
|
||||||
A primitive lock is a synchronization primitive that is not owned
|
|
||||||
by a particular coroutine when locked. A primitive lock is in one
|
|
||||||
of two states, 'locked' or 'unlocked'.
|
|
||||||
|
|
||||||
It is created in the unlocked state. It has two basic methods,
|
|
||||||
acquire() and release(). When the state is unlocked, acquire()
|
|
||||||
changes the state to locked and returns immediately. When the
|
|
||||||
state is locked, acquire() blocks until a call to release() in
|
|
||||||
another coroutine changes it to unlocked, then the acquire() call
|
|
||||||
resets it to locked and returns. The release() method should only
|
|
||||||
be called in the locked state; it changes the state to unlocked
|
|
||||||
and returns immediately. If an attempt is made to release an
|
|
||||||
unlocked lock, a RuntimeError will be raised.
|
|
||||||
|
|
||||||
When more than one coroutine is blocked in acquire() waiting for
|
|
||||||
the state to turn to unlocked, only one coroutine proceeds when a
|
|
||||||
release() call resets the state to unlocked; first coroutine which
|
|
||||||
is blocked in acquire() is being processed.
|
|
||||||
|
|
||||||
acquire() is a coroutine and should be called with 'yield from'.
|
|
||||||
|
|
||||||
Locks also support the context management protocol. '(yield from lock)'
|
|
||||||
should be used as the context manager expression.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
lock = Lock()
|
|
||||||
...
|
|
||||||
yield from lock
|
|
||||||
try:
|
|
||||||
...
|
|
||||||
finally:
|
|
||||||
lock.release()
|
|
||||||
|
|
||||||
Context manager usage:
|
|
||||||
|
|
||||||
lock = Lock()
|
|
||||||
...
|
|
||||||
with (yield from lock):
|
|
||||||
...
|
|
||||||
|
|
||||||
Lock objects can be tested for locking state:
|
|
||||||
|
|
||||||
if not lock.locked():
|
|
||||||
yield from lock
|
|
||||||
else:
|
|
||||||
# lock is acquired
|
|
||||||
...
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
|
||||||
self._waiters = collections.deque()
|
|
||||||
self._locked = False
|
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
res = super().__repr__()
|
|
||||||
extra = 'locked' if self._locked else 'unlocked'
|
|
||||||
if self._waiters:
|
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
|
||||||
|
|
||||||
def locked(self):
|
|
||||||
"""Return True if lock is acquired."""
|
|
||||||
return self._locked
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def acquire(self):
|
|
||||||
"""Acquire a lock.
|
|
||||||
|
|
||||||
This method blocks until the lock is unlocked, then sets it to
|
|
||||||
locked and returns True.
|
|
||||||
"""
|
|
||||||
if not self._locked and all(w.cancelled() for w in self._waiters):
|
|
||||||
self._locked = True
|
|
||||||
return True
|
|
||||||
|
|
||||||
fut = self._loop.create_future()
|
|
||||||
self._waiters.append(fut)
|
|
||||||
try:
|
|
||||||
yield from fut
|
|
||||||
self._locked = True
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self._waiters.remove(fut)
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
"""Release a lock.
|
|
||||||
|
|
||||||
When the lock is locked, reset it to unlocked, and return.
|
|
||||||
If any other coroutines are blocked waiting for the lock to become
|
|
||||||
unlocked, allow exactly one of them to proceed.
|
|
||||||
|
|
||||||
When invoked on an unlocked lock, a RuntimeError is raised.
|
|
||||||
|
|
||||||
There is no return value.
|
|
||||||
"""
|
|
||||||
if self._locked:
|
|
||||||
self._locked = False
|
|
||||||
# Wake up the first waiter who isn't cancelled.
|
|
||||||
for fut in self._waiters:
|
|
||||||
if not fut.done():
|
|
||||||
fut.set_result(True)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Lock is not acquired.')
|
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
|
||||||
"""Asynchronous equivalent to threading.Event.
|
|
||||||
|
|
||||||
Class implementing event objects. An event manages a flag that can be set
|
|
||||||
to true with the set() method and reset to false with the clear() method.
|
|
||||||
The wait() method blocks until the flag is true. The flag is initially
|
|
||||||
false.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *, loop=None):
|
|
||||||
self._waiters = collections.deque()
|
|
||||||
self._value = False
|
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
res = super().__repr__()
|
|
||||||
extra = 'set' if self._value else 'unset'
|
|
||||||
if self._waiters:
|
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
|
||||||
|
|
||||||
def is_set(self):
|
|
||||||
"""Return True if and only if the internal flag is true."""
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def set(self):
|
|
||||||
"""Set the internal flag to true. All coroutines waiting for it to
|
|
||||||
become true are awakened. Coroutine that call wait() once the flag is
|
|
||||||
true will not block at all.
|
|
||||||
"""
|
|
||||||
if not self._value:
|
|
||||||
self._value = True
|
|
||||||
|
|
||||||
for fut in self._waiters:
|
|
||||||
if not fut.done():
|
|
||||||
fut.set_result(True)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
"""Reset the internal flag to false. Subsequently, coroutines calling
|
|
||||||
wait() will block until set() is called to set the internal flag
|
|
||||||
to true again."""
|
|
||||||
self._value = False
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait(self):
|
|
||||||
"""Block until the internal flag is true.
|
|
||||||
|
|
||||||
If the internal flag is true on entry, return True
|
|
||||||
immediately. Otherwise, block until another coroutine calls
|
|
||||||
set() to set the flag to true, then return True.
|
|
||||||
"""
|
|
||||||
if self._value:
|
|
||||||
return True
|
|
||||||
|
|
||||||
fut = self._loop.create_future()
|
|
||||||
self._waiters.append(fut)
|
|
||||||
try:
|
|
||||||
yield from fut
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self._waiters.remove(fut)
|
|
||||||
|
|
||||||
|
|
||||||
class Condition(_ContextManagerMixin):
|
|
||||||
"""Asynchronous equivalent to threading.Condition.
|
|
||||||
|
|
||||||
This class implements condition variable objects. A condition variable
|
|
||||||
allows one or more coroutines to wait until they are notified by another
|
|
||||||
coroutine.
|
|
||||||
|
|
||||||
A new Lock object is created and used as the underlying lock.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, lock=None, *, loop=None):
|
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
if lock is None:
|
|
||||||
lock = Lock(loop=self._loop)
|
|
||||||
elif lock._loop is not self._loop:
|
|
||||||
raise ValueError("loop argument must agree with lock")
|
|
||||||
|
|
||||||
self._lock = lock
|
|
||||||
# Export the lock's locked(), acquire() and release() methods.
|
|
||||||
self.locked = lock.locked
|
|
||||||
self.acquire = lock.acquire
|
|
||||||
self.release = lock.release
|
|
||||||
|
|
||||||
self._waiters = collections.deque()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
res = super().__repr__()
|
|
||||||
extra = 'locked' if self.locked() else 'unlocked'
|
|
||||||
if self._waiters:
|
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait(self):
|
|
||||||
"""Wait until notified.
|
|
||||||
|
|
||||||
If the calling coroutine has not acquired the lock when this
|
|
||||||
method is called, a RuntimeError is raised.
|
|
||||||
|
|
||||||
This method releases the underlying lock, and then blocks
|
|
||||||
until it is awakened by a notify() or notify_all() call for
|
|
||||||
the same condition variable in another coroutine. Once
|
|
||||||
awakened, it re-acquires the lock and returns True.
|
|
||||||
"""
|
|
||||||
if not self.locked():
|
|
||||||
raise RuntimeError('cannot wait on un-acquired lock')
|
|
||||||
|
|
||||||
self.release()
|
|
||||||
try:
|
|
||||||
fut = self._loop.create_future()
|
|
||||||
self._waiters.append(fut)
|
|
||||||
try:
|
|
||||||
yield from fut
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self._waiters.remove(fut)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Must reacquire lock even if wait is cancelled
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
yield from self.acquire()
|
|
||||||
break
|
|
||||||
except futures.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait_for(self, predicate):
|
|
||||||
"""Wait until a predicate becomes true.
|
|
||||||
|
|
||||||
The predicate should be a callable which result will be
|
|
||||||
interpreted as a boolean value. The final predicate value is
|
|
||||||
the return value.
|
|
||||||
"""
|
|
||||||
result = predicate()
|
|
||||||
while not result:
|
|
||||||
yield from self.wait()
|
|
||||||
result = predicate()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def notify(self, n=1):
|
|
||||||
"""By default, wake up one coroutine waiting on this condition, if any.
|
|
||||||
If the calling coroutine has not acquired the lock when this method
|
|
||||||
is called, a RuntimeError is raised.
|
|
||||||
|
|
||||||
This method wakes up at most n of the coroutines waiting for the
|
|
||||||
condition variable; it is a no-op if no coroutines are waiting.
|
|
||||||
|
|
||||||
Note: an awakened coroutine does not actually return from its
|
|
||||||
wait() call until it can reacquire the lock. Since notify() does
|
|
||||||
not release the lock, its caller should.
|
|
||||||
"""
|
|
||||||
if not self.locked():
|
|
||||||
raise RuntimeError('cannot notify on un-acquired lock')
|
|
||||||
|
|
||||||
idx = 0
|
|
||||||
for fut in self._waiters:
|
|
||||||
if idx >= n:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not fut.done():
|
|
||||||
idx += 1
|
|
||||||
fut.set_result(False)
|
|
||||||
|
|
||||||
def notify_all(self):
|
|
||||||
"""Wake up all threads waiting on this condition. This method acts
|
|
||||||
like notify(), but wakes up all waiting threads instead of one. If the
|
|
||||||
calling thread has not acquired the lock when this method is called,
|
|
||||||
a RuntimeError is raised.
|
|
||||||
"""
|
|
||||||
self.notify(len(self._waiters))
|
|
||||||
|
|
||||||
|
|
||||||
class Semaphore(_ContextManagerMixin):
|
|
||||||
"""A Semaphore implementation.
|
|
||||||
|
|
||||||
A semaphore manages an internal counter which is decremented by each
|
|
||||||
acquire() call and incremented by each release() call. The counter
|
|
||||||
can never go below zero; when acquire() finds that it is zero, it blocks,
|
|
||||||
waiting until some other thread calls release().
|
|
||||||
|
|
||||||
Semaphores also support the context management protocol.
|
|
||||||
|
|
||||||
The optional argument gives the initial value for the internal
|
|
||||||
counter; it defaults to 1. If the value given is less than 0,
|
|
||||||
ValueError is raised.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, value=1, *, loop=None):
|
|
||||||
if value < 0:
|
|
||||||
raise ValueError("Semaphore initial value must be >= 0")
|
|
||||||
self._value = value
|
|
||||||
self._waiters = collections.deque()
|
|
||||||
if loop is not None:
|
|
||||||
self._loop = loop
|
|
||||||
else:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
res = super().__repr__()
|
|
||||||
extra = 'locked' if self.locked() else 'unlocked,value:{}'.format(
|
|
||||||
self._value)
|
|
||||||
if self._waiters:
|
|
||||||
extra = '{},waiters:{}'.format(extra, len(self._waiters))
|
|
||||||
return '<{} [{}]>'.format(res[1:-1], extra)
|
|
||||||
|
|
||||||
def _wake_up_next(self):
|
|
||||||
while self._waiters:
|
|
||||||
waiter = self._waiters.popleft()
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
def locked(self):
|
|
||||||
"""Returns True if semaphore can not be acquired immediately."""
|
|
||||||
return self._value == 0
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def acquire(self):
|
|
||||||
"""Acquire a semaphore.
|
|
||||||
|
|
||||||
If the internal counter is larger than zero on entry,
|
|
||||||
decrement it by one and return True immediately. If it is
|
|
||||||
zero on entry, block, waiting until some other coroutine has
|
|
||||||
called release() to make it larger than 0, and then return
|
|
||||||
True.
|
|
||||||
"""
|
|
||||||
while self._value <= 0:
|
|
||||||
fut = self._loop.create_future()
|
|
||||||
self._waiters.append(fut)
|
|
||||||
try:
|
|
||||||
yield from fut
|
|
||||||
except:
|
|
||||||
# See the similar code in Queue.get.
|
|
||||||
fut.cancel()
|
|
||||||
if self._value > 0 and not fut.cancelled():
|
|
||||||
self._wake_up_next()
|
|
||||||
raise
|
|
||||||
self._value -= 1
|
|
||||||
return True
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
"""Release a semaphore, incrementing the internal counter by one.
|
|
||||||
When it was zero on entry and another coroutine is waiting for it to
|
|
||||||
become larger than zero again, wake up that coroutine.
|
|
||||||
"""
|
|
||||||
self._value += 1
|
|
||||||
self._wake_up_next()
|
|
||||||
|
|
||||||
|
|
||||||
class BoundedSemaphore(Semaphore):
|
|
||||||
"""A bounded semaphore implementation.
|
|
||||||
|
|
||||||
This raises ValueError in release() if it would increase the value
|
|
||||||
above the initial value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, value=1, *, loop=None):
|
|
||||||
self._bound_value = value
|
|
||||||
super().__init__(value, loop=loop)
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
if self._value >= self._bound_value:
|
|
||||||
raise ValueError('BoundedSemaphore released too many times')
|
|
||||||
super().release()
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
"""Logging configuration."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
# Name the logger after the package.
|
|
||||||
logger = logging.getLogger(__package__)
|
|
||||||
@@ -1,550 +0,0 @@
|
|||||||
"""Event loop using a proactor and related classes.
|
|
||||||
|
|
||||||
A proactor is a "notify-on-completion" multiplexer. Currently a
|
|
||||||
proactor is only implemented on Windows with IOCP.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['BaseProactorEventLoop']
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import base_events
|
|
||||||
from . import compat
|
|
||||||
from . import constants
|
|
||||||
from . import futures
|
|
||||||
from . import sslproto
|
|
||||||
from . import transports
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorBasePipeTransport(transports._FlowControlMixin,
|
|
||||||
transports.BaseTransport):
|
|
||||||
"""Base class for pipe and socket transports."""
|
|
||||||
|
|
||||||
def __init__(self, loop, sock, protocol, waiter=None,
|
|
||||||
extra=None, server=None):
|
|
||||||
super().__init__(extra, loop)
|
|
||||||
self._set_extra(sock)
|
|
||||||
self._sock = sock
|
|
||||||
self._protocol = protocol
|
|
||||||
self._server = server
|
|
||||||
self._buffer = None # None or bytearray.
|
|
||||||
self._read_fut = None
|
|
||||||
self._write_fut = None
|
|
||||||
self._pending_write = 0
|
|
||||||
self._conn_lost = 0
|
|
||||||
self._closing = False # Set when close() called.
|
|
||||||
self._eof_written = False
|
|
||||||
if self._server is not None:
|
|
||||||
self._server._attach()
|
|
||||||
self._loop.call_soon(self._protocol.connection_made, self)
|
|
||||||
if waiter is not None:
|
|
||||||
# only wake up the waiter when connection_made() has been called
|
|
||||||
self._loop.call_soon(futures._set_result_unless_cancelled,
|
|
||||||
waiter, None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = [self.__class__.__name__]
|
|
||||||
if self._sock is None:
|
|
||||||
info.append('closed')
|
|
||||||
elif self._closing:
|
|
||||||
info.append('closing')
|
|
||||||
if self._sock is not None:
|
|
||||||
info.append('fd=%s' % self._sock.fileno())
|
|
||||||
if self._read_fut is not None:
|
|
||||||
info.append('read=%s' % self._read_fut)
|
|
||||||
if self._write_fut is not None:
|
|
||||||
info.append("write=%r" % self._write_fut)
|
|
||||||
if self._buffer:
|
|
||||||
bufsize = len(self._buffer)
|
|
||||||
info.append('write_bufsize=%s' % bufsize)
|
|
||||||
if self._eof_written:
|
|
||||||
info.append('EOF written')
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
def _set_extra(self, sock):
|
|
||||||
self._extra['pipe'] = sock
|
|
||||||
|
|
||||||
def set_protocol(self, protocol):
|
|
||||||
self._protocol = protocol
|
|
||||||
|
|
||||||
def get_protocol(self):
|
|
||||||
return self._protocol
|
|
||||||
|
|
||||||
def is_closing(self):
|
|
||||||
return self._closing
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._closing:
|
|
||||||
return
|
|
||||||
self._closing = True
|
|
||||||
self._conn_lost += 1
|
|
||||||
if not self._buffer and self._write_fut is None:
|
|
||||||
self._loop.call_soon(self._call_connection_lost, None)
|
|
||||||
if self._read_fut is not None:
|
|
||||||
self._read_fut.cancel()
|
|
||||||
self._read_fut = None
|
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
|
||||||
if self._sock is not None:
|
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def _fatal_error(self, exc, message='Fatal error on pipe transport'):
|
|
||||||
if isinstance(exc, base_events._FATAL_ERROR_IGNORE):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r: %s", self, message, exc_info=True)
|
|
||||||
else:
|
|
||||||
self._loop.call_exception_handler({
|
|
||||||
'message': message,
|
|
||||||
'exception': exc,
|
|
||||||
'transport': self,
|
|
||||||
'protocol': self._protocol,
|
|
||||||
})
|
|
||||||
self._force_close(exc)
|
|
||||||
|
|
||||||
def _force_close(self, exc):
|
|
||||||
if self._closing:
|
|
||||||
return
|
|
||||||
self._closing = True
|
|
||||||
self._conn_lost += 1
|
|
||||||
if self._write_fut:
|
|
||||||
self._write_fut.cancel()
|
|
||||||
self._write_fut = None
|
|
||||||
if self._read_fut:
|
|
||||||
self._read_fut.cancel()
|
|
||||||
self._read_fut = None
|
|
||||||
self._pending_write = 0
|
|
||||||
self._buffer = None
|
|
||||||
self._loop.call_soon(self._call_connection_lost, exc)
|
|
||||||
|
|
||||||
def _call_connection_lost(self, exc):
|
|
||||||
try:
|
|
||||||
self._protocol.connection_lost(exc)
|
|
||||||
finally:
|
|
||||||
# XXX If there is a pending overlapped read on the other
|
|
||||||
# end then it may fail with ERROR_NETNAME_DELETED if we
|
|
||||||
# just close our end. First calling shutdown() seems to
|
|
||||||
# cure it, but maybe using DisconnectEx() would be better.
|
|
||||||
if hasattr(self._sock, 'shutdown'):
|
|
||||||
self._sock.shutdown(socket.SHUT_RDWR)
|
|
||||||
self._sock.close()
|
|
||||||
self._sock = None
|
|
||||||
server = self._server
|
|
||||||
if server is not None:
|
|
||||||
server._detach()
|
|
||||||
self._server = None
|
|
||||||
|
|
||||||
def get_write_buffer_size(self):
|
|
||||||
size = self._pending_write
|
|
||||||
if self._buffer is not None:
|
|
||||||
size += len(self._buffer)
|
|
||||||
return size
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorReadPipeTransport(_ProactorBasePipeTransport,
|
|
||||||
transports.ReadTransport):
|
|
||||||
"""Transport for read pipes."""
|
|
||||||
|
|
||||||
def __init__(self, loop, sock, protocol, waiter=None,
|
|
||||||
extra=None, server=None):
|
|
||||||
super().__init__(loop, sock, protocol, waiter, extra, server)
|
|
||||||
self._paused = False
|
|
||||||
self._loop.call_soon(self._loop_reading)
|
|
||||||
|
|
||||||
def pause_reading(self):
|
|
||||||
if self._closing:
|
|
||||||
raise RuntimeError('Cannot pause_reading() when closing')
|
|
||||||
if self._paused:
|
|
||||||
raise RuntimeError('Already paused')
|
|
||||||
self._paused = True
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r pauses reading", self)
|
|
||||||
|
|
||||||
def resume_reading(self):
|
|
||||||
if not self._paused:
|
|
||||||
raise RuntimeError('Not paused')
|
|
||||||
self._paused = False
|
|
||||||
if self._closing:
|
|
||||||
return
|
|
||||||
self._loop.call_soon(self._loop_reading, self._read_fut)
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r resumes reading", self)
|
|
||||||
|
|
||||||
def _loop_reading(self, fut=None):
|
|
||||||
if self._paused:
|
|
||||||
return
|
|
||||||
data = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
if fut is not None:
|
|
||||||
assert self._read_fut is fut or (self._read_fut is None and
|
|
||||||
self._closing)
|
|
||||||
self._read_fut = None
|
|
||||||
data = fut.result() # deliver data later in "finally" clause
|
|
||||||
|
|
||||||
if self._closing:
|
|
||||||
# since close() has been called we ignore any read data
|
|
||||||
data = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if data == b'':
|
|
||||||
# we got end-of-file so no need to reschedule a new read
|
|
||||||
return
|
|
||||||
|
|
||||||
# reschedule a new read
|
|
||||||
self._read_fut = self._loop._proactor.recv(self._sock, 4096)
|
|
||||||
except ConnectionAbortedError as exc:
|
|
||||||
if not self._closing:
|
|
||||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
|
||||||
elif self._loop.get_debug():
|
|
||||||
logger.debug("Read error on pipe transport while closing",
|
|
||||||
exc_info=True)
|
|
||||||
except ConnectionResetError as exc:
|
|
||||||
self._force_close(exc)
|
|
||||||
except OSError as exc:
|
|
||||||
self._fatal_error(exc, 'Fatal read error on pipe transport')
|
|
||||||
except futures.CancelledError:
|
|
||||||
if not self._closing:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
self._read_fut.add_done_callback(self._loop_reading)
|
|
||||||
finally:
|
|
||||||
if data:
|
|
||||||
self._protocol.data_received(data)
|
|
||||||
elif data is not None:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r received EOF", self)
|
|
||||||
keep_open = self._protocol.eof_received()
|
|
||||||
if not keep_open:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport,
|
|
||||||
transports.WriteTransport):
|
|
||||||
"""Transport for write pipes."""
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
if not isinstance(data, (bytes, bytearray, memoryview)):
|
|
||||||
raise TypeError('data argument must be byte-ish (%r)',
|
|
||||||
type(data))
|
|
||||||
if self._eof_written:
|
|
||||||
raise RuntimeError('write_eof() already called')
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._conn_lost:
|
|
||||||
if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
|
|
||||||
logger.warning('socket.send() raised exception.')
|
|
||||||
self._conn_lost += 1
|
|
||||||
return
|
|
||||||
|
|
||||||
# Observable states:
|
|
||||||
# 1. IDLE: _write_fut and _buffer both None
|
|
||||||
# 2. WRITING: _write_fut set; _buffer None
|
|
||||||
# 3. BACKED UP: _write_fut set; _buffer a bytearray
|
|
||||||
# We always copy the data, so the caller can't modify it
|
|
||||||
# while we're still waiting for the I/O to happen.
|
|
||||||
if self._write_fut is None: # IDLE -> WRITING
|
|
||||||
assert self._buffer is None
|
|
||||||
# Pass a copy, except if it's already immutable.
|
|
||||||
self._loop_writing(data=bytes(data))
|
|
||||||
elif not self._buffer: # WRITING -> BACKED UP
|
|
||||||
# Make a mutable copy which we can extend.
|
|
||||||
self._buffer = bytearray(data)
|
|
||||||
self._maybe_pause_protocol()
|
|
||||||
else: # BACKED UP
|
|
||||||
# Append to buffer (also copies).
|
|
||||||
self._buffer.extend(data)
|
|
||||||
self._maybe_pause_protocol()
|
|
||||||
|
|
||||||
def _loop_writing(self, f=None, data=None):
|
|
||||||
try:
|
|
||||||
assert f is self._write_fut
|
|
||||||
self._write_fut = None
|
|
||||||
self._pending_write = 0
|
|
||||||
if f:
|
|
||||||
f.result()
|
|
||||||
if data is None:
|
|
||||||
data = self._buffer
|
|
||||||
self._buffer = None
|
|
||||||
if not data:
|
|
||||||
if self._closing:
|
|
||||||
self._loop.call_soon(self._call_connection_lost, None)
|
|
||||||
if self._eof_written:
|
|
||||||
self._sock.shutdown(socket.SHUT_WR)
|
|
||||||
# Now that we've reduced the buffer size, tell the
|
|
||||||
# protocol to resume writing if it was paused. Note that
|
|
||||||
# we do this last since the callback is called immediately
|
|
||||||
# and it may add more data to the buffer (even causing the
|
|
||||||
# protocol to be paused again).
|
|
||||||
self._maybe_resume_protocol()
|
|
||||||
else:
|
|
||||||
self._write_fut = self._loop._proactor.send(self._sock, data)
|
|
||||||
if not self._write_fut.done():
|
|
||||||
assert self._pending_write == 0
|
|
||||||
self._pending_write = len(data)
|
|
||||||
self._write_fut.add_done_callback(self._loop_writing)
|
|
||||||
self._maybe_pause_protocol()
|
|
||||||
else:
|
|
||||||
self._write_fut.add_done_callback(self._loop_writing)
|
|
||||||
except ConnectionResetError as exc:
|
|
||||||
self._force_close(exc)
|
|
||||||
except OSError as exc:
|
|
||||||
self._fatal_error(exc, 'Fatal write error on pipe transport')
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def write_eof(self):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
self._force_close(None)
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorWritePipeTransport(_ProactorBaseWritePipeTransport):
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
super().__init__(*args, **kw)
|
|
||||||
self._read_fut = self._loop._proactor.recv(self._sock, 16)
|
|
||||||
self._read_fut.add_done_callback(self._pipe_closed)
|
|
||||||
|
|
||||||
def _pipe_closed(self, fut):
|
|
||||||
if fut.cancelled():
|
|
||||||
# the transport has been closed
|
|
||||||
return
|
|
||||||
assert fut.result() == b''
|
|
||||||
if self._closing:
|
|
||||||
assert self._read_fut is None
|
|
||||||
return
|
|
||||||
assert fut is self._read_fut, (fut, self._read_fut)
|
|
||||||
self._read_fut = None
|
|
||||||
if self._write_fut is not None:
|
|
||||||
self._force_close(BrokenPipeError())
|
|
||||||
else:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorDuplexPipeTransport(_ProactorReadPipeTransport,
|
|
||||||
_ProactorBaseWritePipeTransport,
|
|
||||||
transports.Transport):
|
|
||||||
"""Transport for duplex pipes."""
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def write_eof(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class _ProactorSocketTransport(_ProactorReadPipeTransport,
|
|
||||||
_ProactorBaseWritePipeTransport,
|
|
||||||
transports.Transport):
|
|
||||||
"""Transport for connected sockets."""
|
|
||||||
|
|
||||||
def _set_extra(self, sock):
|
|
||||||
self._extra['socket'] = sock
|
|
||||||
try:
|
|
||||||
self._extra['sockname'] = sock.getsockname()
|
|
||||||
except (socket.error, AttributeError):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning("getsockname() failed on %r",
|
|
||||||
sock, exc_info=True)
|
|
||||||
if 'peername' not in self._extra:
|
|
||||||
try:
|
|
||||||
self._extra['peername'] = sock.getpeername()
|
|
||||||
except (socket.error, AttributeError):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning("getpeername() failed on %r",
|
|
||||||
sock, exc_info=True)
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def write_eof(self):
|
|
||||||
if self._closing or self._eof_written:
|
|
||||||
return
|
|
||||||
self._eof_written = True
|
|
||||||
if self._write_fut is None:
|
|
||||||
self._sock.shutdown(socket.SHUT_WR)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProactorEventLoop(base_events.BaseEventLoop):
|
|
||||||
|
|
||||||
def __init__(self, proactor):
|
|
||||||
super().__init__()
|
|
||||||
logger.debug('Using proactor: %s', proactor.__class__.__name__)
|
|
||||||
self._proactor = proactor
|
|
||||||
self._selector = proactor # convenient alias
|
|
||||||
self._self_reading_future = None
|
|
||||||
self._accept_futures = {} # socket file descriptor => Future
|
|
||||||
proactor.set_loop(self)
|
|
||||||
self._make_self_pipe()
|
|
||||||
|
|
||||||
def _make_socket_transport(self, sock, protocol, waiter=None,
|
|
||||||
extra=None, server=None):
|
|
||||||
return _ProactorSocketTransport(self, sock, protocol, waiter,
|
|
||||||
extra, server)
|
|
||||||
|
|
||||||
def _make_ssl_transport(self, rawsock, protocol, sslcontext, waiter=None,
|
|
||||||
*, server_side=False, server_hostname=None,
|
|
||||||
extra=None, server=None):
|
|
||||||
if not sslproto._is_sslproto_available():
|
|
||||||
raise NotImplementedError("Proactor event loop requires Python 3.5"
|
|
||||||
" or newer (ssl.MemoryBIO) to support "
|
|
||||||
"SSL")
|
|
||||||
|
|
||||||
ssl_protocol = sslproto.SSLProtocol(self, protocol, sslcontext, waiter,
|
|
||||||
server_side, server_hostname)
|
|
||||||
_ProactorSocketTransport(self, rawsock, ssl_protocol,
|
|
||||||
extra=extra, server=server)
|
|
||||||
return ssl_protocol._app_transport
|
|
||||||
|
|
||||||
def _make_duplex_pipe_transport(self, sock, protocol, waiter=None,
|
|
||||||
extra=None):
|
|
||||||
return _ProactorDuplexPipeTransport(self,
|
|
||||||
sock, protocol, waiter, extra)
|
|
||||||
|
|
||||||
def _make_read_pipe_transport(self, sock, protocol, waiter=None,
|
|
||||||
extra=None):
|
|
||||||
return _ProactorReadPipeTransport(self, sock, protocol, waiter, extra)
|
|
||||||
|
|
||||||
def _make_write_pipe_transport(self, sock, protocol, waiter=None,
|
|
||||||
extra=None):
|
|
||||||
# We want connection_lost() to be called when other end closes
|
|
||||||
return _ProactorWritePipeTransport(self,
|
|
||||||
sock, protocol, waiter, extra)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self.is_running():
|
|
||||||
raise RuntimeError("Cannot close a running event loop")
|
|
||||||
if self.is_closed():
|
|
||||||
return
|
|
||||||
|
|
||||||
# Call these methods before closing the event loop (before calling
|
|
||||||
# BaseEventLoop.close), because they can schedule callbacks with
|
|
||||||
# call_soon(), which is forbidden when the event loop is closed.
|
|
||||||
self._stop_accept_futures()
|
|
||||||
self._close_self_pipe()
|
|
||||||
self._proactor.close()
|
|
||||||
self._proactor = None
|
|
||||||
self._selector = None
|
|
||||||
|
|
||||||
# Close the event loop
|
|
||||||
super().close()
|
|
||||||
|
|
||||||
def sock_recv(self, sock, n):
|
|
||||||
return self._proactor.recv(sock, n)
|
|
||||||
|
|
||||||
def sock_sendall(self, sock, data):
|
|
||||||
return self._proactor.send(sock, data)
|
|
||||||
|
|
||||||
def sock_connect(self, sock, address):
|
|
||||||
return self._proactor.connect(sock, address)
|
|
||||||
|
|
||||||
def sock_accept(self, sock):
|
|
||||||
return self._proactor.accept(sock)
|
|
||||||
|
|
||||||
def _socketpair(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _close_self_pipe(self):
|
|
||||||
if self._self_reading_future is not None:
|
|
||||||
self._self_reading_future.cancel()
|
|
||||||
self._self_reading_future = None
|
|
||||||
self._ssock.close()
|
|
||||||
self._ssock = None
|
|
||||||
self._csock.close()
|
|
||||||
self._csock = None
|
|
||||||
self._internal_fds -= 1
|
|
||||||
|
|
||||||
def _make_self_pipe(self):
|
|
||||||
# A self-socket, really. :-)
|
|
||||||
self._ssock, self._csock = self._socketpair()
|
|
||||||
self._ssock.setblocking(False)
|
|
||||||
self._csock.setblocking(False)
|
|
||||||
self._internal_fds += 1
|
|
||||||
self.call_soon(self._loop_self_reading)
|
|
||||||
|
|
||||||
def _loop_self_reading(self, f=None):
|
|
||||||
try:
|
|
||||||
if f is not None:
|
|
||||||
f.result() # may raise
|
|
||||||
f = self._proactor.recv(self._ssock, 4096)
|
|
||||||
except futures.CancelledError:
|
|
||||||
# _close_self_pipe() has been called, stop waiting for data
|
|
||||||
return
|
|
||||||
except Exception as exc:
|
|
||||||
self.call_exception_handler({
|
|
||||||
'message': 'Error on reading from the event loop self pipe',
|
|
||||||
'exception': exc,
|
|
||||||
'loop': self,
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
self._self_reading_future = f
|
|
||||||
f.add_done_callback(self._loop_self_reading)
|
|
||||||
|
|
||||||
def _write_to_self(self):
|
|
||||||
self._csock.send(b'\0')
|
|
||||||
|
|
||||||
def _start_serving(self, protocol_factory, sock,
|
|
||||||
sslcontext=None, server=None, backlog=100):
|
|
||||||
|
|
||||||
def loop(f=None):
|
|
||||||
try:
|
|
||||||
if f is not None:
|
|
||||||
conn, addr = f.result()
|
|
||||||
if self._debug:
|
|
||||||
logger.debug("%r got a new connection from %r: %r",
|
|
||||||
server, addr, conn)
|
|
||||||
protocol = protocol_factory()
|
|
||||||
if sslcontext is not None:
|
|
||||||
self._make_ssl_transport(
|
|
||||||
conn, protocol, sslcontext, server_side=True,
|
|
||||||
extra={'peername': addr}, server=server)
|
|
||||||
else:
|
|
||||||
self._make_socket_transport(
|
|
||||||
conn, protocol,
|
|
||||||
extra={'peername': addr}, server=server)
|
|
||||||
if self.is_closed():
|
|
||||||
return
|
|
||||||
f = self._proactor.accept(sock)
|
|
||||||
except OSError as exc:
|
|
||||||
if sock.fileno() != -1:
|
|
||||||
self.call_exception_handler({
|
|
||||||
'message': 'Accept failed on a socket',
|
|
||||||
'exception': exc,
|
|
||||||
'socket': sock,
|
|
||||||
})
|
|
||||||
sock.close()
|
|
||||||
elif self._debug:
|
|
||||||
logger.debug("Accept failed on socket %r",
|
|
||||||
sock, exc_info=True)
|
|
||||||
except futures.CancelledError:
|
|
||||||
sock.close()
|
|
||||||
else:
|
|
||||||
self._accept_futures[sock.fileno()] = f
|
|
||||||
f.add_done_callback(loop)
|
|
||||||
|
|
||||||
self.call_soon(loop)
|
|
||||||
|
|
||||||
def _process_events(self, event_list):
|
|
||||||
# Events are processed in the IocpProactor._poll() method
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _stop_accept_futures(self):
|
|
||||||
for future in self._accept_futures.values():
|
|
||||||
future.cancel()
|
|
||||||
self._accept_futures.clear()
|
|
||||||
|
|
||||||
def _stop_serving(self, sock):
|
|
||||||
self._stop_accept_futures()
|
|
||||||
self._proactor._stop_serving(sock)
|
|
||||||
sock.close()
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
"""Abstract Protocol class."""
|
|
||||||
|
|
||||||
__all__ = ['BaseProtocol', 'Protocol', 'DatagramProtocol',
|
|
||||||
'SubprocessProtocol']
|
|
||||||
|
|
||||||
|
|
||||||
class BaseProtocol:
|
|
||||||
"""Common base class for protocol interfaces.
|
|
||||||
|
|
||||||
Usually user implements protocols that derived from BaseProtocol
|
|
||||||
like Protocol or ProcessProtocol.
|
|
||||||
|
|
||||||
The only case when BaseProtocol should be implemented directly is
|
|
||||||
write-only transport like write pipe
|
|
||||||
"""
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
"""Called when a connection is made.
|
|
||||||
|
|
||||||
The argument is the transport representing the pipe connection.
|
|
||||||
To receive data, wait for data_received() calls.
|
|
||||||
When the connection is closed, connection_lost() is called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
"""Called when the connection is lost or closed.
|
|
||||||
|
|
||||||
The argument is an exception object or None (the latter
|
|
||||||
meaning a regular EOF is received or the connection was
|
|
||||||
aborted or closed).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def pause_writing(self):
|
|
||||||
"""Called when the transport's buffer goes over the high-water mark.
|
|
||||||
|
|
||||||
Pause and resume calls are paired -- pause_writing() is called
|
|
||||||
once when the buffer goes strictly over the high-water mark
|
|
||||||
(even if subsequent writes increases the buffer size even
|
|
||||||
more), and eventually resume_writing() is called once when the
|
|
||||||
buffer size reaches the low-water mark.
|
|
||||||
|
|
||||||
Note that if the buffer size equals the high-water mark,
|
|
||||||
pause_writing() is not called -- it must go strictly over.
|
|
||||||
Conversely, resume_writing() is called when the buffer size is
|
|
||||||
equal or lower than the low-water mark. These end conditions
|
|
||||||
are important to ensure that things go as expected when either
|
|
||||||
mark is zero.
|
|
||||||
|
|
||||||
NOTE: This is the only Protocol callback that is not called
|
|
||||||
through EventLoop.call_soon() -- if it were, it would have no
|
|
||||||
effect when it's most needed (when the app keeps writing
|
|
||||||
without yielding until pause_writing() is called).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def resume_writing(self):
|
|
||||||
"""Called when the transport's buffer drains below the low-water mark.
|
|
||||||
|
|
||||||
See pause_writing() for details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Protocol(BaseProtocol):
|
|
||||||
"""Interface for stream protocol.
|
|
||||||
|
|
||||||
The user should implement this interface. They can inherit from
|
|
||||||
this class but don't need to. The implementations here do
|
|
||||||
nothing (they don't raise exceptions).
|
|
||||||
|
|
||||||
When the user wants to requests a transport, they pass a protocol
|
|
||||||
factory to a utility function (e.g., EventLoop.create_connection()).
|
|
||||||
|
|
||||||
When the connection is made successfully, connection_made() is
|
|
||||||
called with a suitable transport object. Then data_received()
|
|
||||||
will be called 0 or more times with data (bytes) received from the
|
|
||||||
transport; finally, connection_lost() will be called exactly once
|
|
||||||
with either an exception object or None as an argument.
|
|
||||||
|
|
||||||
State machine of calls:
|
|
||||||
|
|
||||||
start -> CM [-> DR*] [-> ER?] -> CL -> end
|
|
||||||
|
|
||||||
* CM: connection_made()
|
|
||||||
* DR: data_received()
|
|
||||||
* ER: eof_received()
|
|
||||||
* CL: connection_lost()
|
|
||||||
"""
|
|
||||||
|
|
||||||
def data_received(self, data):
|
|
||||||
"""Called when some data is received.
|
|
||||||
|
|
||||||
The argument is a bytes object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def eof_received(self):
|
|
||||||
"""Called when the other end calls write_eof() or equivalent.
|
|
||||||
|
|
||||||
If this returns a false value (including None), the transport
|
|
||||||
will close itself. If it returns a true value, closing the
|
|
||||||
transport is up to the protocol.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DatagramProtocol(BaseProtocol):
|
|
||||||
"""Interface for datagram protocol."""
|
|
||||||
|
|
||||||
def datagram_received(self, data, addr):
|
|
||||||
"""Called when some datagram is received."""
|
|
||||||
|
|
||||||
def error_received(self, exc):
|
|
||||||
"""Called when a send or receive operation raises an OSError.
|
|
||||||
|
|
||||||
(Other than BlockingIOError or InterruptedError.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class SubprocessProtocol(BaseProtocol):
|
|
||||||
"""Interface for protocol for subprocess calls."""
|
|
||||||
|
|
||||||
def pipe_data_received(self, fd, data):
|
|
||||||
"""Called when the subprocess writes data into stdout/stderr pipe.
|
|
||||||
|
|
||||||
fd is int file descriptor.
|
|
||||||
data is bytes object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def pipe_connection_lost(self, fd, exc):
|
|
||||||
"""Called when a file descriptor associated with the child process is
|
|
||||||
closed.
|
|
||||||
|
|
||||||
fd is the int file descriptor that was closed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def process_exited(self):
|
|
||||||
"""Called when subprocess has exited."""
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
"""Queues"""
|
|
||||||
|
|
||||||
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty']
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import heapq
|
|
||||||
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import locks
|
|
||||||
from .coroutines import coroutine
|
|
||||||
|
|
||||||
|
|
||||||
class QueueEmpty(Exception):
|
|
||||||
"""Exception raised when Queue.get_nowait() is called on a Queue object
|
|
||||||
which is empty.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class QueueFull(Exception):
|
|
||||||
"""Exception raised when the Queue.put_nowait() method is called on a Queue
|
|
||||||
object which is full.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Queue:
|
|
||||||
"""A queue, useful for coordinating producer and consumer coroutines.
|
|
||||||
|
|
||||||
If maxsize is less than or equal to zero, the queue size is infinite. If it
|
|
||||||
is an integer greater than 0, then "yield from put()" will block when the
|
|
||||||
queue reaches maxsize, until an item is removed by get().
|
|
||||||
|
|
||||||
Unlike the standard library Queue, you can reliably know this Queue's size
|
|
||||||
with qsize(), since your single-threaded asyncio application won't be
|
|
||||||
interrupted between calling qsize() and doing an operation on the Queue.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, maxsize=0, *, loop=None):
|
|
||||||
if loop is None:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
else:
|
|
||||||
self._loop = loop
|
|
||||||
self._maxsize = maxsize
|
|
||||||
|
|
||||||
# Futures.
|
|
||||||
self._getters = collections.deque()
|
|
||||||
# Futures.
|
|
||||||
self._putters = collections.deque()
|
|
||||||
self._unfinished_tasks = 0
|
|
||||||
self._finished = locks.Event(loop=self._loop)
|
|
||||||
self._finished.set()
|
|
||||||
self._init(maxsize)
|
|
||||||
|
|
||||||
# These three are overridable in subclasses.
|
|
||||||
|
|
||||||
def _init(self, maxsize):
|
|
||||||
self._queue = collections.deque()
|
|
||||||
|
|
||||||
def _get(self):
|
|
||||||
return self._queue.popleft()
|
|
||||||
|
|
||||||
def _put(self, item):
|
|
||||||
self._queue.append(item)
|
|
||||||
|
|
||||||
# End of the overridable methods.
|
|
||||||
|
|
||||||
def _wakeup_next(self, waiters):
|
|
||||||
# Wake up the next waiter (if any) that isn't cancelled.
|
|
||||||
while waiters:
|
|
||||||
waiter = waiters.popleft()
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
break
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<{} at {:#x} {}>'.format(
|
|
||||||
type(self).__name__, id(self), self._format())
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '<{} {}>'.format(type(self).__name__, self._format())
|
|
||||||
|
|
||||||
def _format(self):
|
|
||||||
result = 'maxsize={!r}'.format(self._maxsize)
|
|
||||||
if getattr(self, '_queue', None):
|
|
||||||
result += ' _queue={!r}'.format(list(self._queue))
|
|
||||||
if self._getters:
|
|
||||||
result += ' _getters[{}]'.format(len(self._getters))
|
|
||||||
if self._putters:
|
|
||||||
result += ' _putters[{}]'.format(len(self._putters))
|
|
||||||
if self._unfinished_tasks:
|
|
||||||
result += ' tasks={}'.format(self._unfinished_tasks)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def qsize(self):
|
|
||||||
"""Number of items in the queue."""
|
|
||||||
return len(self._queue)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def maxsize(self):
|
|
||||||
"""Number of items allowed in the queue."""
|
|
||||||
return self._maxsize
|
|
||||||
|
|
||||||
def empty(self):
|
|
||||||
"""Return True if the queue is empty, False otherwise."""
|
|
||||||
return not self._queue
|
|
||||||
|
|
||||||
def full(self):
|
|
||||||
"""Return True if there are maxsize items in the queue.
|
|
||||||
|
|
||||||
Note: if the Queue was initialized with maxsize=0 (the default),
|
|
||||||
then full() is never True.
|
|
||||||
"""
|
|
||||||
if self._maxsize <= 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return self.qsize() >= self._maxsize
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def put(self, item):
|
|
||||||
"""Put an item into the queue.
|
|
||||||
|
|
||||||
Put an item into the queue. If the queue is full, wait until a free
|
|
||||||
slot is available before adding item.
|
|
||||||
|
|
||||||
This method is a coroutine.
|
|
||||||
"""
|
|
||||||
while self.full():
|
|
||||||
putter = self._loop.create_future()
|
|
||||||
self._putters.append(putter)
|
|
||||||
try:
|
|
||||||
yield from putter
|
|
||||||
except:
|
|
||||||
putter.cancel() # Just in case putter is not done yet.
|
|
||||||
if not self.full() and not putter.cancelled():
|
|
||||||
# We were woken up by get_nowait(), but can't take
|
|
||||||
# the call. Wake up the next in line.
|
|
||||||
self._wakeup_next(self._putters)
|
|
||||||
raise
|
|
||||||
return self.put_nowait(item)
|
|
||||||
|
|
||||||
def put_nowait(self, item):
|
|
||||||
"""Put an item into the queue without blocking.
|
|
||||||
|
|
||||||
If no free slot is immediately available, raise QueueFull.
|
|
||||||
"""
|
|
||||||
if self.full():
|
|
||||||
raise QueueFull
|
|
||||||
self._put(item)
|
|
||||||
self._unfinished_tasks += 1
|
|
||||||
self._finished.clear()
|
|
||||||
self._wakeup_next(self._getters)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def get(self):
|
|
||||||
"""Remove and return an item from the queue.
|
|
||||||
|
|
||||||
If queue is empty, wait until an item is available.
|
|
||||||
|
|
||||||
This method is a coroutine.
|
|
||||||
"""
|
|
||||||
while self.empty():
|
|
||||||
getter = self._loop.create_future()
|
|
||||||
self._getters.append(getter)
|
|
||||||
try:
|
|
||||||
yield from getter
|
|
||||||
except:
|
|
||||||
getter.cancel() # Just in case getter is not done yet.
|
|
||||||
if not self.empty() and not getter.cancelled():
|
|
||||||
# We were woken up by put_nowait(), but can't take
|
|
||||||
# the call. Wake up the next in line.
|
|
||||||
self._wakeup_next(self._getters)
|
|
||||||
raise
|
|
||||||
return self.get_nowait()
|
|
||||||
|
|
||||||
def get_nowait(self):
|
|
||||||
"""Remove and return an item from the queue.
|
|
||||||
|
|
||||||
Return an item if one is immediately available, else raise QueueEmpty.
|
|
||||||
"""
|
|
||||||
if self.empty():
|
|
||||||
raise QueueEmpty
|
|
||||||
item = self._get()
|
|
||||||
self._wakeup_next(self._putters)
|
|
||||||
return item
|
|
||||||
|
|
||||||
def task_done(self):
|
|
||||||
"""Indicate that a formerly enqueued task is complete.
|
|
||||||
|
|
||||||
Used by queue consumers. For each get() used to fetch a task,
|
|
||||||
a subsequent call to task_done() tells the queue that the processing
|
|
||||||
on the task is complete.
|
|
||||||
|
|
||||||
If a join() is currently blocking, it will resume when all items have
|
|
||||||
been processed (meaning that a task_done() call was received for every
|
|
||||||
item that had been put() into the queue).
|
|
||||||
|
|
||||||
Raises ValueError if called more times than there were items placed in
|
|
||||||
the queue.
|
|
||||||
"""
|
|
||||||
if self._unfinished_tasks <= 0:
|
|
||||||
raise ValueError('task_done() called too many times')
|
|
||||||
self._unfinished_tasks -= 1
|
|
||||||
if self._unfinished_tasks == 0:
|
|
||||||
self._finished.set()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def join(self):
|
|
||||||
"""Block until all items in the queue have been gotten and processed.
|
|
||||||
|
|
||||||
The count of unfinished tasks goes up whenever an item is added to the
|
|
||||||
queue. The count goes down whenever a consumer calls task_done() to
|
|
||||||
indicate that the item was retrieved and all work on it is complete.
|
|
||||||
When the count of unfinished tasks drops to zero, join() unblocks.
|
|
||||||
"""
|
|
||||||
if self._unfinished_tasks > 0:
|
|
||||||
yield from self._finished.wait()
|
|
||||||
|
|
||||||
|
|
||||||
class PriorityQueue(Queue):
|
|
||||||
"""A subclass of Queue; retrieves entries in priority order (lowest first).
|
|
||||||
|
|
||||||
Entries are typically tuples of the form: (priority number, data).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _init(self, maxsize):
|
|
||||||
self._queue = []
|
|
||||||
|
|
||||||
def _put(self, item, heappush=heapq.heappush):
|
|
||||||
heappush(self._queue, item)
|
|
||||||
|
|
||||||
def _get(self, heappop=heapq.heappop):
|
|
||||||
return heappop(self._queue)
|
|
||||||
|
|
||||||
|
|
||||||
class LifoQueue(Queue):
|
|
||||||
"""A subclass of Queue that retrieves most recently added entries first."""
|
|
||||||
|
|
||||||
def _init(self, maxsize):
|
|
||||||
self._queue = []
|
|
||||||
|
|
||||||
def _put(self, item):
|
|
||||||
self._queue.append(item)
|
|
||||||
|
|
||||||
def _get(self):
|
|
||||||
return self._queue.pop()
|
|
||||||
|
|
||||||
|
|
||||||
if not compat.PY35:
|
|
||||||
JoinableQueue = Queue
|
|
||||||
"""Deprecated alias for Queue."""
|
|
||||||
__all__.append('JoinableQueue')
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
__all__ = ['run']
|
|
||||||
|
|
||||||
from . import coroutines
|
|
||||||
from . import events
|
|
||||||
from . import tasks
|
|
||||||
|
|
||||||
|
|
||||||
def run(main, *, debug=False):
|
|
||||||
"""Run a coroutine.
|
|
||||||
|
|
||||||
This function runs the passed coroutine, taking care of
|
|
||||||
managing the asyncio event loop and finalizing asynchronous
|
|
||||||
generators.
|
|
||||||
|
|
||||||
This function cannot be called when another asyncio event loop is
|
|
||||||
running in the same thread.
|
|
||||||
|
|
||||||
If debug is True, the event loop will be run in debug mode.
|
|
||||||
|
|
||||||
This function always creates a new event loop and closes it at the end.
|
|
||||||
It should be used as a main entry point for asyncio programs, and should
|
|
||||||
ideally only be called once.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
print('hello')
|
|
||||||
|
|
||||||
asyncio.run(main())
|
|
||||||
"""
|
|
||||||
if events._get_running_loop() is not None:
|
|
||||||
raise RuntimeError(
|
|
||||||
"asyncio.run() cannot be called from a running event loop")
|
|
||||||
|
|
||||||
if not coroutines.iscoroutine(main):
|
|
||||||
raise ValueError("a coroutine was expected, got {!r}".format(main))
|
|
||||||
|
|
||||||
loop = events.new_event_loop()
|
|
||||||
try:
|
|
||||||
events.set_event_loop(loop)
|
|
||||||
loop.set_debug(debug)
|
|
||||||
return loop.run_until_complete(main)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
_cancel_all_tasks(loop)
|
|
||||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
|
||||||
finally:
|
|
||||||
events.set_event_loop(None)
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _cancel_all_tasks(loop):
|
|
||||||
to_cancel = tasks.all_tasks(loop)
|
|
||||||
if not to_cancel:
|
|
||||||
return
|
|
||||||
|
|
||||||
for task in to_cancel:
|
|
||||||
task.cancel()
|
|
||||||
|
|
||||||
loop.run_until_complete(
|
|
||||||
tasks.gather(*to_cancel, loop=loop, return_exceptions=True))
|
|
||||||
|
|
||||||
for task in to_cancel:
|
|
||||||
if task.cancelled():
|
|
||||||
continue
|
|
||||||
if task.exception() is not None:
|
|
||||||
loop.call_exception_handler({
|
|
||||||
'message': 'unhandled exception during asyncio.run() shutdown',
|
|
||||||
'exception': task.exception(),
|
|
||||||
'task': task,
|
|
||||||
})
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,692 +0,0 @@
|
|||||||
import collections
|
|
||||||
import warnings
|
|
||||||
try:
|
|
||||||
import ssl
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
ssl = None
|
|
||||||
|
|
||||||
from . import base_events
|
|
||||||
from . import compat
|
|
||||||
from . import protocols
|
|
||||||
from . import transports
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
def _create_transport_context(server_side, server_hostname):
|
|
||||||
if server_side:
|
|
||||||
raise ValueError('Server side SSL needs a valid SSLContext')
|
|
||||||
|
|
||||||
# Client side may pass ssl=True to use a default
|
|
||||||
# context; in that case the sslcontext passed is None.
|
|
||||||
# The default is secure for client connections.
|
|
||||||
if hasattr(ssl, 'create_default_context'):
|
|
||||||
# Python 3.4+: use up-to-date strong settings.
|
|
||||||
sslcontext = ssl.create_default_context()
|
|
||||||
if not server_hostname:
|
|
||||||
sslcontext.check_hostname = False
|
|
||||||
else:
|
|
||||||
# Fallback for Python 3.3.
|
|
||||||
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
sslcontext.options |= ssl.OP_NO_SSLv2
|
|
||||||
sslcontext.options |= ssl.OP_NO_SSLv3
|
|
||||||
sslcontext.set_default_verify_paths()
|
|
||||||
sslcontext.verify_mode = ssl.CERT_REQUIRED
|
|
||||||
return sslcontext
|
|
||||||
|
|
||||||
|
|
||||||
def _is_sslproto_available():
|
|
||||||
return hasattr(ssl, "MemoryBIO")
|
|
||||||
|
|
||||||
|
|
||||||
# States of an _SSLPipe.
|
|
||||||
_UNWRAPPED = "UNWRAPPED"
|
|
||||||
_DO_HANDSHAKE = "DO_HANDSHAKE"
|
|
||||||
_WRAPPED = "WRAPPED"
|
|
||||||
_SHUTDOWN = "SHUTDOWN"
|
|
||||||
|
|
||||||
|
|
||||||
class _SSLPipe(object):
|
|
||||||
"""An SSL "Pipe".
|
|
||||||
|
|
||||||
An SSL pipe allows you to communicate with an SSL/TLS protocol instance
|
|
||||||
through memory buffers. It can be used to implement a security layer for an
|
|
||||||
existing connection where you don't have access to the connection's file
|
|
||||||
descriptor, or for some reason you don't want to use it.
|
|
||||||
|
|
||||||
An SSL pipe can be in "wrapped" and "unwrapped" mode. In unwrapped mode,
|
|
||||||
data is passed through untransformed. In wrapped mode, application level
|
|
||||||
data is encrypted to SSL record level data and vice versa. The SSL record
|
|
||||||
level is the lowest level in the SSL protocol suite and is what travels
|
|
||||||
as-is over the wire.
|
|
||||||
|
|
||||||
An SslPipe initially is in "unwrapped" mode. To start SSL, call
|
|
||||||
do_handshake(). To shutdown SSL again, call unwrap().
|
|
||||||
"""
|
|
||||||
|
|
||||||
max_size = 256 * 1024 # Buffer size passed to read()
|
|
||||||
|
|
||||||
def __init__(self, context, server_side, server_hostname=None):
|
|
||||||
"""
|
|
||||||
The *context* argument specifies the ssl.SSLContext to use.
|
|
||||||
|
|
||||||
The *server_side* argument indicates whether this is a server side or
|
|
||||||
client side transport.
|
|
||||||
|
|
||||||
The optional *server_hostname* argument can be used to specify the
|
|
||||||
hostname you are connecting to. You may only specify this parameter if
|
|
||||||
the _ssl module supports Server Name Indication (SNI).
|
|
||||||
"""
|
|
||||||
self._context = context
|
|
||||||
self._server_side = server_side
|
|
||||||
self._server_hostname = server_hostname
|
|
||||||
self._state = _UNWRAPPED
|
|
||||||
self._incoming = ssl.MemoryBIO()
|
|
||||||
self._outgoing = ssl.MemoryBIO()
|
|
||||||
self._sslobj = None
|
|
||||||
self._need_ssldata = False
|
|
||||||
self._handshake_cb = None
|
|
||||||
self._shutdown_cb = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def context(self):
|
|
||||||
"""The SSL context passed to the constructor."""
|
|
||||||
return self._context
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ssl_object(self):
|
|
||||||
"""The internal ssl.SSLObject instance.
|
|
||||||
|
|
||||||
Return None if the pipe is not wrapped.
|
|
||||||
"""
|
|
||||||
return self._sslobj
|
|
||||||
|
|
||||||
@property
|
|
||||||
def need_ssldata(self):
|
|
||||||
"""Whether more record level data is needed to complete a handshake
|
|
||||||
that is currently in progress."""
|
|
||||||
return self._need_ssldata
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wrapped(self):
|
|
||||||
"""
|
|
||||||
Whether a security layer is currently in effect.
|
|
||||||
|
|
||||||
Return False during handshake.
|
|
||||||
"""
|
|
||||||
return self._state == _WRAPPED
|
|
||||||
|
|
||||||
def do_handshake(self, callback=None):
|
|
||||||
"""Start the SSL handshake.
|
|
||||||
|
|
||||||
Return a list of ssldata. A ssldata element is a list of buffers
|
|
||||||
|
|
||||||
The optional *callback* argument can be used to install a callback that
|
|
||||||
will be called when the handshake is complete. The callback will be
|
|
||||||
called with None if successful, else an exception instance.
|
|
||||||
"""
|
|
||||||
if self._state != _UNWRAPPED:
|
|
||||||
raise RuntimeError('handshake in progress or completed')
|
|
||||||
self._sslobj = self._context.wrap_bio(
|
|
||||||
self._incoming, self._outgoing,
|
|
||||||
server_side=self._server_side,
|
|
||||||
server_hostname=self._server_hostname)
|
|
||||||
self._state = _DO_HANDSHAKE
|
|
||||||
self._handshake_cb = callback
|
|
||||||
ssldata, appdata = self.feed_ssldata(b'', only_handshake=True)
|
|
||||||
assert len(appdata) == 0
|
|
||||||
return ssldata
|
|
||||||
|
|
||||||
def shutdown(self, callback=None):
|
|
||||||
"""Start the SSL shutdown sequence.
|
|
||||||
|
|
||||||
Return a list of ssldata. A ssldata element is a list of buffers
|
|
||||||
|
|
||||||
The optional *callback* argument can be used to install a callback that
|
|
||||||
will be called when the shutdown is complete. The callback will be
|
|
||||||
called without arguments.
|
|
||||||
"""
|
|
||||||
if self._state == _UNWRAPPED:
|
|
||||||
raise RuntimeError('no security layer present')
|
|
||||||
if self._state == _SHUTDOWN:
|
|
||||||
raise RuntimeError('shutdown in progress')
|
|
||||||
assert self._state in (_WRAPPED, _DO_HANDSHAKE)
|
|
||||||
self._state = _SHUTDOWN
|
|
||||||
self._shutdown_cb = callback
|
|
||||||
ssldata, appdata = self.feed_ssldata(b'')
|
|
||||||
assert appdata == [] or appdata == [b'']
|
|
||||||
return ssldata
|
|
||||||
|
|
||||||
def feed_eof(self):
|
|
||||||
"""Send a potentially "ragged" EOF.
|
|
||||||
|
|
||||||
This method will raise an SSL_ERROR_EOF exception if the EOF is
|
|
||||||
unexpected.
|
|
||||||
"""
|
|
||||||
self._incoming.write_eof()
|
|
||||||
ssldata, appdata = self.feed_ssldata(b'')
|
|
||||||
assert appdata == [] or appdata == [b'']
|
|
||||||
|
|
||||||
def feed_ssldata(self, data, only_handshake=False):
|
|
||||||
"""Feed SSL record level data into the pipe.
|
|
||||||
|
|
||||||
The data must be a bytes instance. It is OK to send an empty bytes
|
|
||||||
instance. This can be used to get ssldata for a handshake initiated by
|
|
||||||
this endpoint.
|
|
||||||
|
|
||||||
Return a (ssldata, appdata) tuple. The ssldata element is a list of
|
|
||||||
buffers containing SSL data that needs to be sent to the remote SSL.
|
|
||||||
|
|
||||||
The appdata element is a list of buffers containing plaintext data that
|
|
||||||
needs to be forwarded to the application. The appdata list may contain
|
|
||||||
an empty buffer indicating an SSL "close_notify" alert. This alert must
|
|
||||||
be acknowledged by calling shutdown().
|
|
||||||
"""
|
|
||||||
if self._state == _UNWRAPPED:
|
|
||||||
# If unwrapped, pass plaintext data straight through.
|
|
||||||
if data:
|
|
||||||
appdata = [data]
|
|
||||||
else:
|
|
||||||
appdata = []
|
|
||||||
return ([], appdata)
|
|
||||||
|
|
||||||
self._need_ssldata = False
|
|
||||||
if data:
|
|
||||||
self._incoming.write(data)
|
|
||||||
|
|
||||||
ssldata = []
|
|
||||||
appdata = []
|
|
||||||
try:
|
|
||||||
if self._state == _DO_HANDSHAKE:
|
|
||||||
# Call do_handshake() until it doesn't raise anymore.
|
|
||||||
self._sslobj.do_handshake()
|
|
||||||
self._state = _WRAPPED
|
|
||||||
if self._handshake_cb:
|
|
||||||
self._handshake_cb(None)
|
|
||||||
if only_handshake:
|
|
||||||
return (ssldata, appdata)
|
|
||||||
# Handshake done: execute the wrapped block
|
|
||||||
|
|
||||||
if self._state == _WRAPPED:
|
|
||||||
# Main state: read data from SSL until close_notify
|
|
||||||
while True:
|
|
||||||
chunk = self._sslobj.read(self.max_size)
|
|
||||||
appdata.append(chunk)
|
|
||||||
if not chunk: # close_notify
|
|
||||||
break
|
|
||||||
|
|
||||||
elif self._state == _SHUTDOWN:
|
|
||||||
# Call shutdown() until it doesn't raise anymore.
|
|
||||||
self._sslobj.unwrap()
|
|
||||||
self._sslobj = None
|
|
||||||
self._state = _UNWRAPPED
|
|
||||||
if self._shutdown_cb:
|
|
||||||
self._shutdown_cb()
|
|
||||||
|
|
||||||
elif self._state == _UNWRAPPED:
|
|
||||||
# Drain possible plaintext data after close_notify.
|
|
||||||
appdata.append(self._incoming.read())
|
|
||||||
except (ssl.SSLError, ssl.CertificateError) as exc:
|
|
||||||
if getattr(exc, 'errno', None) not in (
|
|
||||||
ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE,
|
|
||||||
ssl.SSL_ERROR_SYSCALL):
|
|
||||||
if self._state == _DO_HANDSHAKE and self._handshake_cb:
|
|
||||||
self._handshake_cb(exc)
|
|
||||||
raise
|
|
||||||
self._need_ssldata = (exc.errno == ssl.SSL_ERROR_WANT_READ)
|
|
||||||
|
|
||||||
# Check for record level data that needs to be sent back.
|
|
||||||
# Happens for the initial handshake and renegotiations.
|
|
||||||
if self._outgoing.pending:
|
|
||||||
ssldata.append(self._outgoing.read())
|
|
||||||
return (ssldata, appdata)
|
|
||||||
|
|
||||||
def feed_appdata(self, data, offset=0):
|
|
||||||
"""Feed plaintext data into the pipe.
|
|
||||||
|
|
||||||
Return an (ssldata, offset) tuple. The ssldata element is a list of
|
|
||||||
buffers containing record level data that needs to be sent to the
|
|
||||||
remote SSL instance. The offset is the number of plaintext bytes that
|
|
||||||
were processed, which may be less than the length of data.
|
|
||||||
|
|
||||||
NOTE: In case of short writes, this call MUST be retried with the SAME
|
|
||||||
buffer passed into the *data* argument (i.e. the id() must be the
|
|
||||||
same). This is an OpenSSL requirement. A further particularity is that
|
|
||||||
a short write will always have offset == 0, because the _ssl module
|
|
||||||
does not enable partial writes. And even though the offset is zero,
|
|
||||||
there will still be encrypted data in ssldata.
|
|
||||||
"""
|
|
||||||
assert 0 <= offset <= len(data)
|
|
||||||
if self._state == _UNWRAPPED:
|
|
||||||
# pass through data in unwrapped mode
|
|
||||||
if offset < len(data):
|
|
||||||
ssldata = [data[offset:]]
|
|
||||||
else:
|
|
||||||
ssldata = []
|
|
||||||
return (ssldata, len(data))
|
|
||||||
|
|
||||||
ssldata = []
|
|
||||||
view = memoryview(data)
|
|
||||||
while True:
|
|
||||||
self._need_ssldata = False
|
|
||||||
try:
|
|
||||||
if offset < len(view):
|
|
||||||
offset += self._sslobj.write(view[offset:])
|
|
||||||
except ssl.SSLError as exc:
|
|
||||||
# It is not allowed to call write() after unwrap() until the
|
|
||||||
# close_notify is acknowledged. We return the condition to the
|
|
||||||
# caller as a short write.
|
|
||||||
if exc.reason == 'PROTOCOL_IS_SHUTDOWN':
|
|
||||||
exc.errno = ssl.SSL_ERROR_WANT_READ
|
|
||||||
if exc.errno not in (ssl.SSL_ERROR_WANT_READ,
|
|
||||||
ssl.SSL_ERROR_WANT_WRITE,
|
|
||||||
ssl.SSL_ERROR_SYSCALL):
|
|
||||||
raise
|
|
||||||
self._need_ssldata = (exc.errno == ssl.SSL_ERROR_WANT_READ)
|
|
||||||
|
|
||||||
# See if there's any record level data back for us.
|
|
||||||
if self._outgoing.pending:
|
|
||||||
ssldata.append(self._outgoing.read())
|
|
||||||
if offset == len(view) or self._need_ssldata:
|
|
||||||
break
|
|
||||||
return (ssldata, offset)
|
|
||||||
|
|
||||||
|
|
||||||
class _SSLProtocolTransport(transports._FlowControlMixin,
|
|
||||||
transports.Transport):
|
|
||||||
|
|
||||||
def __init__(self, loop, ssl_protocol, app_protocol):
|
|
||||||
self._loop = loop
|
|
||||||
# SSLProtocol instance
|
|
||||||
self._ssl_protocol = ssl_protocol
|
|
||||||
self._app_protocol = app_protocol
|
|
||||||
self._closed = False
|
|
||||||
|
|
||||||
def get_extra_info(self, name, default=None):
|
|
||||||
"""Get optional transport information."""
|
|
||||||
return self._ssl_protocol._get_extra_info(name, default)
|
|
||||||
|
|
||||||
def set_protocol(self, protocol):
|
|
||||||
self._app_protocol = protocol
|
|
||||||
|
|
||||||
def get_protocol(self):
|
|
||||||
return self._app_protocol
|
|
||||||
|
|
||||||
def is_closing(self):
|
|
||||||
return self._closed
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the transport.
|
|
||||||
|
|
||||||
Buffered data will be flushed asynchronously. No more data
|
|
||||||
will be received. After all buffered data is flushed, the
|
|
||||||
protocol's connection_lost() method will (eventually) called
|
|
||||||
with None as its argument.
|
|
||||||
"""
|
|
||||||
self._closed = True
|
|
||||||
self._ssl_protocol._start_shutdown()
|
|
||||||
|
|
||||||
# On Python 3.3 and older, objects with a destructor part of a reference
|
|
||||||
# cycle are never destroyed. It's not more the case on Python 3.4 thanks
|
|
||||||
# to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
|
||||||
if not self._closed:
|
|
||||||
warnings.warn("unclosed transport %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def pause_reading(self):
|
|
||||||
"""Pause the receiving end.
|
|
||||||
|
|
||||||
No data will be passed to the protocol's data_received()
|
|
||||||
method until resume_reading() is called.
|
|
||||||
"""
|
|
||||||
self._ssl_protocol._transport.pause_reading()
|
|
||||||
|
|
||||||
def resume_reading(self):
|
|
||||||
"""Resume the receiving end.
|
|
||||||
|
|
||||||
Data received will once again be passed to the protocol's
|
|
||||||
data_received() method.
|
|
||||||
"""
|
|
||||||
self._ssl_protocol._transport.resume_reading()
|
|
||||||
|
|
||||||
def set_write_buffer_limits(self, high=None, low=None):
|
|
||||||
"""Set the high- and low-water limits for write flow control.
|
|
||||||
|
|
||||||
These two values control when to call the protocol's
|
|
||||||
pause_writing() and resume_writing() methods. If specified,
|
|
||||||
the low-water limit must be less than or equal to the
|
|
||||||
high-water limit. Neither value can be negative.
|
|
||||||
|
|
||||||
The defaults are implementation-specific. If only the
|
|
||||||
high-water limit is given, the low-water limit defaults to an
|
|
||||||
implementation-specific value less than or equal to the
|
|
||||||
high-water limit. Setting high to zero forces low to zero as
|
|
||||||
well, and causes pause_writing() to be called whenever the
|
|
||||||
buffer becomes non-empty. Setting low to zero causes
|
|
||||||
resume_writing() to be called only once the buffer is empty.
|
|
||||||
Use of zero for either limit is generally sub-optimal as it
|
|
||||||
reduces opportunities for doing I/O and computation
|
|
||||||
concurrently.
|
|
||||||
"""
|
|
||||||
self._ssl_protocol._transport.set_write_buffer_limits(high, low)
|
|
||||||
|
|
||||||
def get_write_buffer_size(self):
|
|
||||||
"""Return the current size of the write buffer."""
|
|
||||||
return self._ssl_protocol._transport.get_write_buffer_size()
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
"""Write some data bytes to the transport.
|
|
||||||
|
|
||||||
This does not block; it buffers the data and arranges for it
|
|
||||||
to be sent out asynchronously.
|
|
||||||
"""
|
|
||||||
if not isinstance(data, (bytes, bytearray, memoryview)):
|
|
||||||
raise TypeError("data: expecting a bytes-like instance, got {!r}"
|
|
||||||
.format(type(data).__name__))
|
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
self._ssl_protocol._write_appdata(data)
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
"""Return True if this transport supports write_eof(), False if not."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
"""Close the transport immediately.
|
|
||||||
|
|
||||||
Buffered data will be lost. No more data will be received.
|
|
||||||
The protocol's connection_lost() method will (eventually) be
|
|
||||||
called with None as its argument.
|
|
||||||
"""
|
|
||||||
self._ssl_protocol._abort()
|
|
||||||
|
|
||||||
|
|
||||||
class SSLProtocol(protocols.Protocol):
|
|
||||||
"""SSL protocol.
|
|
||||||
|
|
||||||
Implementation of SSL on top of a socket using incoming and outgoing
|
|
||||||
buffers which are ssl.MemoryBIO objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, loop, app_protocol, sslcontext, waiter,
|
|
||||||
server_side=False, server_hostname=None,
|
|
||||||
call_connection_made=True):
|
|
||||||
if ssl is None:
|
|
||||||
raise RuntimeError('stdlib ssl module not available')
|
|
||||||
|
|
||||||
if not sslcontext:
|
|
||||||
sslcontext = _create_transport_context(server_side, server_hostname)
|
|
||||||
|
|
||||||
self._server_side = server_side
|
|
||||||
if server_hostname and not server_side:
|
|
||||||
self._server_hostname = server_hostname
|
|
||||||
else:
|
|
||||||
self._server_hostname = None
|
|
||||||
self._sslcontext = sslcontext
|
|
||||||
# SSL-specific extra info. More info are set when the handshake
|
|
||||||
# completes.
|
|
||||||
self._extra = dict(sslcontext=sslcontext)
|
|
||||||
|
|
||||||
# App data write buffering
|
|
||||||
self._write_backlog = collections.deque()
|
|
||||||
self._write_buffer_size = 0
|
|
||||||
|
|
||||||
self._waiter = waiter
|
|
||||||
self._loop = loop
|
|
||||||
self._app_protocol = app_protocol
|
|
||||||
self._app_transport = _SSLProtocolTransport(self._loop,
|
|
||||||
self, self._app_protocol)
|
|
||||||
# _SSLPipe instance (None until the connection is made)
|
|
||||||
self._sslpipe = None
|
|
||||||
self._session_established = False
|
|
||||||
self._in_handshake = False
|
|
||||||
self._in_shutdown = False
|
|
||||||
# transport, ex: SelectorSocketTransport
|
|
||||||
self._transport = None
|
|
||||||
self._call_connection_made = call_connection_made
|
|
||||||
|
|
||||||
def _wakeup_waiter(self, exc=None):
|
|
||||||
if self._waiter is None:
|
|
||||||
return
|
|
||||||
if not self._waiter.cancelled():
|
|
||||||
if exc is not None:
|
|
||||||
self._waiter.set_exception(exc)
|
|
||||||
else:
|
|
||||||
self._waiter.set_result(None)
|
|
||||||
self._waiter = None
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
"""Called when the low-level connection is made.
|
|
||||||
|
|
||||||
Start the SSL handshake.
|
|
||||||
"""
|
|
||||||
self._transport = transport
|
|
||||||
self._sslpipe = _SSLPipe(self._sslcontext,
|
|
||||||
self._server_side,
|
|
||||||
self._server_hostname)
|
|
||||||
self._start_handshake()
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
"""Called when the low-level connection is lost or closed.
|
|
||||||
|
|
||||||
The argument is an exception object or None (the latter
|
|
||||||
meaning a regular EOF is received or the connection was
|
|
||||||
aborted or closed).
|
|
||||||
"""
|
|
||||||
if self._session_established:
|
|
||||||
self._session_established = False
|
|
||||||
self._loop.call_soon(self._app_protocol.connection_lost, exc)
|
|
||||||
self._transport = None
|
|
||||||
self._app_transport = None
|
|
||||||
self._wakeup_waiter(exc)
|
|
||||||
|
|
||||||
def pause_writing(self):
|
|
||||||
"""Called when the low-level transport's buffer goes over
|
|
||||||
the high-water mark.
|
|
||||||
"""
|
|
||||||
self._app_protocol.pause_writing()
|
|
||||||
|
|
||||||
def resume_writing(self):
|
|
||||||
"""Called when the low-level transport's buffer drains below
|
|
||||||
the low-water mark.
|
|
||||||
"""
|
|
||||||
self._app_protocol.resume_writing()
|
|
||||||
|
|
||||||
def data_received(self, data):
|
|
||||||
"""Called when some SSL data is received.
|
|
||||||
|
|
||||||
The argument is a bytes object.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
ssldata, appdata = self._sslpipe.feed_ssldata(data)
|
|
||||||
except ssl.SSLError as e:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.warning('%r: SSL error %s (reason %s)',
|
|
||||||
self, e.errno, e.reason)
|
|
||||||
self._abort()
|
|
||||||
return
|
|
||||||
|
|
||||||
for chunk in ssldata:
|
|
||||||
self._transport.write(chunk)
|
|
||||||
|
|
||||||
for chunk in appdata:
|
|
||||||
if chunk:
|
|
||||||
self._app_protocol.data_received(chunk)
|
|
||||||
else:
|
|
||||||
self._start_shutdown()
|
|
||||||
break
|
|
||||||
|
|
||||||
def eof_received(self):
|
|
||||||
"""Called when the other end of the low-level stream
|
|
||||||
is half-closed.
|
|
||||||
|
|
||||||
If this returns a false value (including None), the transport
|
|
||||||
will close itself. If it returns a true value, closing the
|
|
||||||
transport is up to the protocol.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r received EOF", self)
|
|
||||||
|
|
||||||
self._wakeup_waiter(ConnectionResetError)
|
|
||||||
|
|
||||||
if not self._in_handshake:
|
|
||||||
keep_open = self._app_protocol.eof_received()
|
|
||||||
if keep_open:
|
|
||||||
logger.warning('returning true from eof_received() '
|
|
||||||
'has no effect when using ssl')
|
|
||||||
finally:
|
|
||||||
self._transport.close()
|
|
||||||
|
|
||||||
def _get_extra_info(self, name, default=None):
|
|
||||||
if name in self._extra:
|
|
||||||
return self._extra[name]
|
|
||||||
else:
|
|
||||||
return self._transport.get_extra_info(name, default)
|
|
||||||
|
|
||||||
def _start_shutdown(self):
|
|
||||||
if self._in_shutdown:
|
|
||||||
return
|
|
||||||
self._in_shutdown = True
|
|
||||||
self._write_appdata(b'')
|
|
||||||
|
|
||||||
def _write_appdata(self, data):
|
|
||||||
self._write_backlog.append((data, 0))
|
|
||||||
self._write_buffer_size += len(data)
|
|
||||||
self._process_write_backlog()
|
|
||||||
|
|
||||||
def _start_handshake(self):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r starts SSL handshake", self)
|
|
||||||
self._handshake_start_time = self._loop.time()
|
|
||||||
else:
|
|
||||||
self._handshake_start_time = None
|
|
||||||
self._in_handshake = True
|
|
||||||
# (b'', 1) is a special value in _process_write_backlog() to do
|
|
||||||
# the SSL handshake
|
|
||||||
self._write_backlog.append((b'', 1))
|
|
||||||
self._loop.call_soon(self._process_write_backlog)
|
|
||||||
|
|
||||||
def _on_handshake_complete(self, handshake_exc):
|
|
||||||
self._in_handshake = False
|
|
||||||
|
|
||||||
sslobj = self._sslpipe.ssl_object
|
|
||||||
try:
|
|
||||||
if handshake_exc is not None:
|
|
||||||
raise handshake_exc
|
|
||||||
|
|
||||||
peercert = sslobj.getpeercert()
|
|
||||||
if not hasattr(self._sslcontext, 'check_hostname'):
|
|
||||||
# Verify hostname if requested, Python 3.4+ uses check_hostname
|
|
||||||
# and checks the hostname in do_handshake()
|
|
||||||
if (self._server_hostname
|
|
||||||
and self._sslcontext.verify_mode != ssl.CERT_NONE):
|
|
||||||
ssl.match_hostname(peercert, self._server_hostname)
|
|
||||||
except BaseException as exc:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
if isinstance(exc, ssl.CertificateError):
|
|
||||||
logger.warning("%r: SSL handshake failed "
|
|
||||||
"on verifying the certificate",
|
|
||||||
self, exc_info=True)
|
|
||||||
else:
|
|
||||||
logger.warning("%r: SSL handshake failed",
|
|
||||||
self, exc_info=True)
|
|
||||||
self._transport.close()
|
|
||||||
if isinstance(exc, Exception):
|
|
||||||
self._wakeup_waiter(exc)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if self._loop.get_debug():
|
|
||||||
dt = self._loop.time() - self._handshake_start_time
|
|
||||||
logger.debug("%r: SSL handshake took %.1f ms", self, dt * 1e3)
|
|
||||||
|
|
||||||
# Add extra info that becomes available after handshake.
|
|
||||||
self._extra.update(peercert=peercert,
|
|
||||||
cipher=sslobj.cipher(),
|
|
||||||
compression=sslobj.compression(),
|
|
||||||
ssl_object=sslobj,
|
|
||||||
)
|
|
||||||
if self._call_connection_made:
|
|
||||||
self._app_protocol.connection_made(self._app_transport)
|
|
||||||
self._wakeup_waiter()
|
|
||||||
self._session_established = True
|
|
||||||
# In case transport.write() was already called. Don't call
|
|
||||||
# immediately _process_write_backlog(), but schedule it:
|
|
||||||
# _on_handshake_complete() can be called indirectly from
|
|
||||||
# _process_write_backlog(), and _process_write_backlog() is not
|
|
||||||
# reentrant.
|
|
||||||
self._loop.call_soon(self._process_write_backlog)
|
|
||||||
|
|
||||||
def _process_write_backlog(self):
|
|
||||||
# Try to make progress on the write backlog.
|
|
||||||
if self._transport is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
for i in range(len(self._write_backlog)):
|
|
||||||
data, offset = self._write_backlog[0]
|
|
||||||
if data:
|
|
||||||
ssldata, offset = self._sslpipe.feed_appdata(data, offset)
|
|
||||||
elif offset:
|
|
||||||
ssldata = self._sslpipe.do_handshake(
|
|
||||||
self._on_handshake_complete)
|
|
||||||
offset = 1
|
|
||||||
else:
|
|
||||||
ssldata = self._sslpipe.shutdown(self._finalize)
|
|
||||||
offset = 1
|
|
||||||
|
|
||||||
for chunk in ssldata:
|
|
||||||
self._transport.write(chunk)
|
|
||||||
|
|
||||||
if offset < len(data):
|
|
||||||
self._write_backlog[0] = (data, offset)
|
|
||||||
# A short write means that a write is blocked on a read
|
|
||||||
# We need to enable reading if it is paused!
|
|
||||||
assert self._sslpipe.need_ssldata
|
|
||||||
if self._transport._paused:
|
|
||||||
self._transport.resume_reading()
|
|
||||||
break
|
|
||||||
|
|
||||||
# An entire chunk from the backlog was processed. We can
|
|
||||||
# delete it and reduce the outstanding buffer size.
|
|
||||||
del self._write_backlog[0]
|
|
||||||
self._write_buffer_size -= len(data)
|
|
||||||
except BaseException as exc:
|
|
||||||
if self._in_handshake:
|
|
||||||
# BaseExceptions will be re-raised in _on_handshake_complete.
|
|
||||||
self._on_handshake_complete(exc)
|
|
||||||
else:
|
|
||||||
self._fatal_error(exc, 'Fatal error on SSL transport')
|
|
||||||
if not isinstance(exc, Exception):
|
|
||||||
# BaseException
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _fatal_error(self, exc, message='Fatal error on transport'):
|
|
||||||
# Should be called from exception handler only.
|
|
||||||
if isinstance(exc, base_events._FATAL_ERROR_IGNORE):
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r: %s", self, message, exc_info=True)
|
|
||||||
else:
|
|
||||||
self._loop.call_exception_handler({
|
|
||||||
'message': message,
|
|
||||||
'exception': exc,
|
|
||||||
'transport': self._transport,
|
|
||||||
'protocol': self,
|
|
||||||
})
|
|
||||||
if self._transport:
|
|
||||||
self._transport._force_close(exc)
|
|
||||||
|
|
||||||
def _finalize(self):
|
|
||||||
if self._transport is not None:
|
|
||||||
self._transport.close()
|
|
||||||
|
|
||||||
def _abort(self):
|
|
||||||
if self._transport is not None:
|
|
||||||
try:
|
|
||||||
self._transport.abort()
|
|
||||||
finally:
|
|
||||||
self._finalize()
|
|
||||||
@@ -1,695 +0,0 @@
|
|||||||
"""Stream-related things."""
|
|
||||||
|
|
||||||
__all__ = ['StreamReader', 'StreamWriter', 'StreamReaderProtocol',
|
|
||||||
'open_connection', 'start_server',
|
|
||||||
'IncompleteReadError',
|
|
||||||
'LimitOverrunError',
|
|
||||||
]
|
|
||||||
|
|
||||||
import socket
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
|
||||||
__all__.extend(['open_unix_connection', 'start_unix_server'])
|
|
||||||
|
|
||||||
from . import coroutines
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import protocols
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
_DEFAULT_LIMIT = 2 ** 16
|
|
||||||
|
|
||||||
|
|
||||||
class IncompleteReadError(EOFError):
|
|
||||||
"""
|
|
||||||
Incomplete read error. Attributes:
|
|
||||||
|
|
||||||
- partial: read bytes string before the end of stream was reached
|
|
||||||
- expected: total number of expected bytes (or None if unknown)
|
|
||||||
"""
|
|
||||||
def __init__(self, partial, expected):
|
|
||||||
super().__init__("%d bytes read on a total of %r expected bytes"
|
|
||||||
% (len(partial), expected))
|
|
||||||
self.partial = partial
|
|
||||||
self.expected = expected
|
|
||||||
|
|
||||||
|
|
||||||
class LimitOverrunError(Exception):
|
|
||||||
"""Reached the buffer limit while looking for a separator.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
- consumed: total number of to be consumed bytes.
|
|
||||||
"""
|
|
||||||
def __init__(self, message, consumed):
|
|
||||||
super().__init__(message)
|
|
||||||
self.consumed = consumed
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def open_connection(host=None, port=None, *,
|
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""A wrapper for create_connection() returning a (reader, writer) pair.
|
|
||||||
|
|
||||||
The reader returned is a StreamReader instance; the writer is a
|
|
||||||
StreamWriter instance.
|
|
||||||
|
|
||||||
The arguments are all the usual arguments to create_connection()
|
|
||||||
except protocol_factory; most common are positional host and port,
|
|
||||||
with various optional keyword arguments following.
|
|
||||||
|
|
||||||
Additional optional keyword arguments are loop (to set the event loop
|
|
||||||
instance to use) and limit (to set the buffer limit passed to the
|
|
||||||
StreamReader).
|
|
||||||
|
|
||||||
(If you want to customize the StreamReader and/or
|
|
||||||
StreamReaderProtocol classes, just copy the code -- there's
|
|
||||||
really nothing special here except some convenience.)
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
|
||||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
|
||||||
transport, _ = yield from loop.create_connection(
|
|
||||||
lambda: protocol, host, port, **kwds)
|
|
||||||
writer = StreamWriter(transport, protocol, reader, loop)
|
|
||||||
return reader, writer
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def start_server(client_connected_cb, host=None, port=None, *,
|
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Start a socket server, call back for each client connected.
|
|
||||||
|
|
||||||
The first parameter, `client_connected_cb`, takes two parameters:
|
|
||||||
client_reader, client_writer. client_reader is a StreamReader
|
|
||||||
object, while client_writer is a StreamWriter object. This
|
|
||||||
parameter can either be a plain callback function or a coroutine;
|
|
||||||
if it is a coroutine, it will be automatically converted into a
|
|
||||||
Task.
|
|
||||||
|
|
||||||
The rest of the arguments are all the usual arguments to
|
|
||||||
loop.create_server() except protocol_factory; most common are
|
|
||||||
positional host and port, with various optional keyword arguments
|
|
||||||
following. The return value is the same as loop.create_server().
|
|
||||||
|
|
||||||
Additional optional keyword arguments are loop (to set the event loop
|
|
||||||
instance to use) and limit (to set the buffer limit passed to the
|
|
||||||
StreamReader).
|
|
||||||
|
|
||||||
The return value is the same as loop.create_server(), i.e. a
|
|
||||||
Server object which can be used to stop the service.
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def factory():
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
|
||||||
protocol = StreamReaderProtocol(reader, client_connected_cb,
|
|
||||||
loop=loop)
|
|
||||||
return protocol
|
|
||||||
|
|
||||||
return (yield from loop.create_server(factory, host, port, **kwds))
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
|
||||||
# UNIX Domain Sockets are supported on this platform
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def open_unix_connection(path=None, *,
|
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Similar to `open_connection` but works with UNIX Domain Sockets."""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
|
||||||
protocol = StreamReaderProtocol(reader, loop=loop)
|
|
||||||
transport, _ = yield from loop.create_unix_connection(
|
|
||||||
lambda: protocol, path, **kwds)
|
|
||||||
writer = StreamWriter(transport, protocol, reader, loop)
|
|
||||||
return reader, writer
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def start_unix_server(client_connected_cb, path=None, *,
|
|
||||||
loop=None, limit=_DEFAULT_LIMIT, **kwds):
|
|
||||||
"""Similar to `start_server` but works with UNIX Domain Sockets."""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
def factory():
|
|
||||||
reader = StreamReader(limit=limit, loop=loop)
|
|
||||||
protocol = StreamReaderProtocol(reader, client_connected_cb,
|
|
||||||
loop=loop)
|
|
||||||
return protocol
|
|
||||||
|
|
||||||
return (yield from loop.create_unix_server(factory, path, **kwds))
|
|
||||||
|
|
||||||
|
|
||||||
class FlowControlMixin(protocols.Protocol):
|
|
||||||
"""Reusable flow control logic for StreamWriter.drain().
|
|
||||||
|
|
||||||
This implements the protocol methods pause_writing(),
|
|
||||||
resume_reading() and connection_lost(). If the subclass overrides
|
|
||||||
these it must call the super methods.
|
|
||||||
|
|
||||||
StreamWriter.drain() must wait for _drain_helper() coroutine.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, loop=None):
|
|
||||||
if loop is None:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
else:
|
|
||||||
self._loop = loop
|
|
||||||
self._paused = False
|
|
||||||
self._drain_waiter = None
|
|
||||||
self._connection_lost = False
|
|
||||||
|
|
||||||
def pause_writing(self):
|
|
||||||
assert not self._paused
|
|
||||||
self._paused = True
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r pauses writing", self)
|
|
||||||
|
|
||||||
def resume_writing(self):
|
|
||||||
assert self._paused
|
|
||||||
self._paused = False
|
|
||||||
if self._loop.get_debug():
|
|
||||||
logger.debug("%r resumes writing", self)
|
|
||||||
|
|
||||||
waiter = self._drain_waiter
|
|
||||||
if waiter is not None:
|
|
||||||
self._drain_waiter = None
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
self._connection_lost = True
|
|
||||||
# Wake up the writer if currently paused.
|
|
||||||
if not self._paused:
|
|
||||||
return
|
|
||||||
waiter = self._drain_waiter
|
|
||||||
if waiter is None:
|
|
||||||
return
|
|
||||||
self._drain_waiter = None
|
|
||||||
if waiter.done():
|
|
||||||
return
|
|
||||||
if exc is None:
|
|
||||||
waiter.set_result(None)
|
|
||||||
else:
|
|
||||||
waiter.set_exception(exc)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _drain_helper(self):
|
|
||||||
if self._connection_lost:
|
|
||||||
raise ConnectionResetError('Connection lost')
|
|
||||||
if not self._paused:
|
|
||||||
return
|
|
||||||
waiter = self._drain_waiter
|
|
||||||
assert waiter is None or waiter.cancelled()
|
|
||||||
waiter = self._loop.create_future()
|
|
||||||
self._drain_waiter = waiter
|
|
||||||
yield from waiter
|
|
||||||
|
|
||||||
|
|
||||||
class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
|
|
||||||
"""Helper class to adapt between Protocol and StreamReader.
|
|
||||||
|
|
||||||
(This is a helper class instead of making StreamReader itself a
|
|
||||||
Protocol subclass, because the StreamReader has other potential
|
|
||||||
uses, and to prevent the user of the StreamReader to accidentally
|
|
||||||
call inappropriate methods of the protocol.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, stream_reader, client_connected_cb=None, loop=None):
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
self._stream_reader = stream_reader
|
|
||||||
self._stream_writer = None
|
|
||||||
self._client_connected_cb = client_connected_cb
|
|
||||||
self._over_ssl = False
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
self._stream_reader.set_transport(transport)
|
|
||||||
self._over_ssl = transport.get_extra_info('sslcontext') is not None
|
|
||||||
if self._client_connected_cb is not None:
|
|
||||||
self._stream_writer = StreamWriter(transport, self,
|
|
||||||
self._stream_reader,
|
|
||||||
self._loop)
|
|
||||||
res = self._client_connected_cb(self._stream_reader,
|
|
||||||
self._stream_writer)
|
|
||||||
if coroutines.iscoroutine(res):
|
|
||||||
self._loop.create_task(res)
|
|
||||||
|
|
||||||
def connection_lost(self, exc):
|
|
||||||
if self._stream_reader is not None:
|
|
||||||
if exc is None:
|
|
||||||
self._stream_reader.feed_eof()
|
|
||||||
else:
|
|
||||||
self._stream_reader.set_exception(exc)
|
|
||||||
super().connection_lost(exc)
|
|
||||||
self._stream_reader = None
|
|
||||||
self._stream_writer = None
|
|
||||||
|
|
||||||
def data_received(self, data):
|
|
||||||
self._stream_reader.feed_data(data)
|
|
||||||
|
|
||||||
def eof_received(self):
|
|
||||||
self._stream_reader.feed_eof()
|
|
||||||
if self._over_ssl:
|
|
||||||
# Prevent a warning in SSLProtocol.eof_received:
|
|
||||||
# "returning true from eof_received()
|
|
||||||
# has no effect when using ssl"
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class StreamWriter:
|
|
||||||
"""Wraps a Transport.
|
|
||||||
|
|
||||||
This exposes write(), writelines(), [can_]write_eof(),
|
|
||||||
get_extra_info() and close(). It adds drain() which returns an
|
|
||||||
optional Future on which you can wait for flow control. It also
|
|
||||||
adds a transport property which references the Transport
|
|
||||||
directly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, transport, protocol, reader, loop):
|
|
||||||
self._transport = transport
|
|
||||||
self._protocol = protocol
|
|
||||||
# drain() expects that the reader has an exception() method
|
|
||||||
assert reader is None or isinstance(reader, StreamReader)
|
|
||||||
self._reader = reader
|
|
||||||
self._loop = loop
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = [self.__class__.__name__, 'transport=%r' % self._transport]
|
|
||||||
if self._reader is not None:
|
|
||||||
info.append('reader=%r' % self._reader)
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def transport(self):
|
|
||||||
return self._transport
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
self._transport.write(data)
|
|
||||||
|
|
||||||
def writelines(self, data):
|
|
||||||
self._transport.writelines(data)
|
|
||||||
|
|
||||||
def write_eof(self):
|
|
||||||
return self._transport.write_eof()
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
return self._transport.can_write_eof()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
return self._transport.close()
|
|
||||||
|
|
||||||
def get_extra_info(self, name, default=None):
|
|
||||||
return self._transport.get_extra_info(name, default)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def drain(self):
|
|
||||||
"""Flush the write buffer.
|
|
||||||
|
|
||||||
The intended use is to write
|
|
||||||
|
|
||||||
w.write(data)
|
|
||||||
yield from w.drain()
|
|
||||||
"""
|
|
||||||
if self._reader is not None:
|
|
||||||
exc = self._reader.exception()
|
|
||||||
if exc is not None:
|
|
||||||
raise exc
|
|
||||||
if self._transport is not None:
|
|
||||||
if self._transport.is_closing():
|
|
||||||
# Yield to the event loop so connection_lost() may be
|
|
||||||
# called. Without this, _drain_helper() would return
|
|
||||||
# immediately, and code that calls
|
|
||||||
# write(...); yield from drain()
|
|
||||||
# in a loop would never call connection_lost(), so it
|
|
||||||
# would not see an error when the socket is closed.
|
|
||||||
yield
|
|
||||||
yield from self._protocol._drain_helper()
|
|
||||||
|
|
||||||
|
|
||||||
class StreamReader:
|
|
||||||
|
|
||||||
def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
|
|
||||||
# The line length limit is a security feature;
|
|
||||||
# it also doubles as half the buffer limit.
|
|
||||||
|
|
||||||
if limit <= 0:
|
|
||||||
raise ValueError('Limit cannot be <= 0')
|
|
||||||
|
|
||||||
self._limit = limit
|
|
||||||
if loop is None:
|
|
||||||
self._loop = events.get_event_loop()
|
|
||||||
else:
|
|
||||||
self._loop = loop
|
|
||||||
self._buffer = bytearray()
|
|
||||||
self._eof = False # Whether we're done.
|
|
||||||
self._waiter = None # A future used by _wait_for_data()
|
|
||||||
self._exception = None
|
|
||||||
self._transport = None
|
|
||||||
self._paused = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = ['StreamReader']
|
|
||||||
if self._buffer:
|
|
||||||
info.append('%d bytes' % len(self._buffer))
|
|
||||||
if self._eof:
|
|
||||||
info.append('eof')
|
|
||||||
if self._limit != _DEFAULT_LIMIT:
|
|
||||||
info.append('l=%d' % self._limit)
|
|
||||||
if self._waiter:
|
|
||||||
info.append('w=%r' % self._waiter)
|
|
||||||
if self._exception:
|
|
||||||
info.append('e=%r' % self._exception)
|
|
||||||
if self._transport:
|
|
||||||
info.append('t=%r' % self._transport)
|
|
||||||
if self._paused:
|
|
||||||
info.append('paused')
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
def exception(self):
|
|
||||||
return self._exception
|
|
||||||
|
|
||||||
def set_exception(self, exc):
|
|
||||||
self._exception = exc
|
|
||||||
|
|
||||||
waiter = self._waiter
|
|
||||||
if waiter is not None:
|
|
||||||
self._waiter = None
|
|
||||||
if not waiter.cancelled():
|
|
||||||
waiter.set_exception(exc)
|
|
||||||
|
|
||||||
def _wakeup_waiter(self):
|
|
||||||
"""Wakeup read*() functions waiting for data or EOF."""
|
|
||||||
waiter = self._waiter
|
|
||||||
if waiter is not None:
|
|
||||||
self._waiter = None
|
|
||||||
if not waiter.cancelled():
|
|
||||||
waiter.set_result(None)
|
|
||||||
|
|
||||||
def set_transport(self, transport):
|
|
||||||
assert self._transport is None, 'Transport already set'
|
|
||||||
self._transport = transport
|
|
||||||
|
|
||||||
def _maybe_resume_transport(self):
|
|
||||||
if self._paused and len(self._buffer) <= self._limit:
|
|
||||||
self._paused = False
|
|
||||||
self._transport.resume_reading()
|
|
||||||
|
|
||||||
def feed_eof(self):
|
|
||||||
self._eof = True
|
|
||||||
self._wakeup_waiter()
|
|
||||||
|
|
||||||
def at_eof(self):
|
|
||||||
"""Return True if the buffer is empty and 'feed_eof' was called."""
|
|
||||||
return self._eof and not self._buffer
|
|
||||||
|
|
||||||
def feed_data(self, data):
|
|
||||||
assert not self._eof, 'feed_data after feed_eof'
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._buffer.extend(data)
|
|
||||||
self._wakeup_waiter()
|
|
||||||
|
|
||||||
if (self._transport is not None and
|
|
||||||
not self._paused and
|
|
||||||
len(self._buffer) > 2 * self._limit):
|
|
||||||
try:
|
|
||||||
self._transport.pause_reading()
|
|
||||||
except NotImplementedError:
|
|
||||||
# The transport can't be paused.
|
|
||||||
# We'll just have to buffer all data.
|
|
||||||
# Forget the transport so we don't keep trying.
|
|
||||||
self._transport = None
|
|
||||||
else:
|
|
||||||
self._paused = True
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wait_for_data(self, func_name):
|
|
||||||
"""Wait until feed_data() or feed_eof() is called.
|
|
||||||
|
|
||||||
If stream was paused, automatically resume it.
|
|
||||||
"""
|
|
||||||
# StreamReader uses a future to link the protocol feed_data() method
|
|
||||||
# to a read coroutine. Running two read coroutines at the same time
|
|
||||||
# would have an unexpected behaviour. It would not possible to know
|
|
||||||
# which coroutine would get the next data.
|
|
||||||
if self._waiter is not None:
|
|
||||||
raise RuntimeError('%s() called while another coroutine is '
|
|
||||||
'already waiting for incoming data' % func_name)
|
|
||||||
|
|
||||||
assert not self._eof, '_wait_for_data after EOF'
|
|
||||||
|
|
||||||
# Waiting for data while paused will make deadlock, so prevent it.
|
|
||||||
# This is essential for readexactly(n) for case when n > self._limit.
|
|
||||||
if self._paused:
|
|
||||||
self._paused = False
|
|
||||||
self._transport.resume_reading()
|
|
||||||
|
|
||||||
self._waiter = self._loop.create_future()
|
|
||||||
try:
|
|
||||||
yield from self._waiter
|
|
||||||
finally:
|
|
||||||
self._waiter = None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def readline(self):
|
|
||||||
"""Read chunk of data from the stream until newline (b'\n') is found.
|
|
||||||
|
|
||||||
On success, return chunk that ends with newline. If only partial
|
|
||||||
line can be read due to EOF, return incomplete line without
|
|
||||||
terminating newline. When EOF was reached while no bytes read, empty
|
|
||||||
bytes object is returned.
|
|
||||||
|
|
||||||
If limit is reached, ValueError will be raised. In that case, if
|
|
||||||
newline was found, complete line including newline will be removed
|
|
||||||
from internal buffer. Else, internal buffer will be cleared. Limit is
|
|
||||||
compared against part of the line without newline.
|
|
||||||
|
|
||||||
If stream was paused, this function will automatically resume it if
|
|
||||||
needed.
|
|
||||||
"""
|
|
||||||
sep = b'\n'
|
|
||||||
seplen = len(sep)
|
|
||||||
try:
|
|
||||||
line = yield from self.readuntil(sep)
|
|
||||||
except IncompleteReadError as e:
|
|
||||||
return e.partial
|
|
||||||
except LimitOverrunError as e:
|
|
||||||
if self._buffer.startswith(sep, e.consumed):
|
|
||||||
del self._buffer[:e.consumed + seplen]
|
|
||||||
else:
|
|
||||||
self._buffer.clear()
|
|
||||||
self._maybe_resume_transport()
|
|
||||||
raise ValueError(e.args[0])
|
|
||||||
return line
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def readuntil(self, separator=b'\n'):
|
|
||||||
"""Read data from the stream until ``separator`` is found.
|
|
||||||
|
|
||||||
On success, the data and separator will be removed from the
|
|
||||||
internal buffer (consumed). Returned data will include the
|
|
||||||
separator at the end.
|
|
||||||
|
|
||||||
Configured stream limit is used to check result. Limit sets the
|
|
||||||
maximal length of data that can be returned, not counting the
|
|
||||||
separator.
|
|
||||||
|
|
||||||
If an EOF occurs and the complete separator is still not found,
|
|
||||||
an IncompleteReadError exception will be raised, and the internal
|
|
||||||
buffer will be reset. The IncompleteReadError.partial attribute
|
|
||||||
may contain the separator partially.
|
|
||||||
|
|
||||||
If the data cannot be read because of over limit, a
|
|
||||||
LimitOverrunError exception will be raised, and the data
|
|
||||||
will be left in the internal buffer, so it can be read again.
|
|
||||||
"""
|
|
||||||
seplen = len(separator)
|
|
||||||
if seplen == 0:
|
|
||||||
raise ValueError('Separator should be at least one-byte string')
|
|
||||||
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
|
|
||||||
# Consume whole buffer except last bytes, which length is
|
|
||||||
# one less than seplen. Let's check corner cases with
|
|
||||||
# separator='SEPARATOR':
|
|
||||||
# * we have received almost complete separator (without last
|
|
||||||
# byte). i.e buffer='some textSEPARATO'. In this case we
|
|
||||||
# can safely consume len(separator) - 1 bytes.
|
|
||||||
# * last byte of buffer is first byte of separator, i.e.
|
|
||||||
# buffer='abcdefghijklmnopqrS'. We may safely consume
|
|
||||||
# everything except that last byte, but this require to
|
|
||||||
# analyze bytes of buffer that match partial separator.
|
|
||||||
# This is slow and/or require FSM. For this case our
|
|
||||||
# implementation is not optimal, since require rescanning
|
|
||||||
# of data that is known to not belong to separator. In
|
|
||||||
# real world, separator will not be so long to notice
|
|
||||||
# performance problems. Even when reading MIME-encoded
|
|
||||||
# messages :)
|
|
||||||
|
|
||||||
# `offset` is the number of bytes from the beginning of the buffer
|
|
||||||
# where there is no occurrence of `separator`.
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
# Loop until we find `separator` in the buffer, exceed the buffer size,
|
|
||||||
# or an EOF has happened.
|
|
||||||
while True:
|
|
||||||
buflen = len(self._buffer)
|
|
||||||
|
|
||||||
# Check if we now have enough data in the buffer for `separator` to
|
|
||||||
# fit.
|
|
||||||
if buflen - offset >= seplen:
|
|
||||||
isep = self._buffer.find(separator, offset)
|
|
||||||
|
|
||||||
if isep != -1:
|
|
||||||
# `separator` is in the buffer. `isep` will be used later
|
|
||||||
# to retrieve the data.
|
|
||||||
break
|
|
||||||
|
|
||||||
# see upper comment for explanation.
|
|
||||||
offset = buflen + 1 - seplen
|
|
||||||
if offset > self._limit:
|
|
||||||
raise LimitOverrunError(
|
|
||||||
'Separator is not found, and chunk exceed the limit',
|
|
||||||
offset)
|
|
||||||
|
|
||||||
# Complete message (with full separator) may be present in buffer
|
|
||||||
# even when EOF flag is set. This may happen when the last chunk
|
|
||||||
# adds data which makes separator be found. That's why we check for
|
|
||||||
# EOF *ater* inspecting the buffer.
|
|
||||||
if self._eof:
|
|
||||||
chunk = bytes(self._buffer)
|
|
||||||
self._buffer.clear()
|
|
||||||
raise IncompleteReadError(chunk, None)
|
|
||||||
|
|
||||||
# _wait_for_data() will resume reading if stream was paused.
|
|
||||||
yield from self._wait_for_data('readuntil')
|
|
||||||
|
|
||||||
if isep > self._limit:
|
|
||||||
raise LimitOverrunError(
|
|
||||||
'Separator is found, but chunk is longer than limit', isep)
|
|
||||||
|
|
||||||
chunk = self._buffer[:isep + seplen]
|
|
||||||
del self._buffer[:isep + seplen]
|
|
||||||
self._maybe_resume_transport()
|
|
||||||
return bytes(chunk)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def read(self, n=-1):
|
|
||||||
"""Read up to `n` bytes from the stream.
|
|
||||||
|
|
||||||
If n is not provided, or set to -1, read until EOF and return all read
|
|
||||||
bytes. If the EOF was received and the internal buffer is empty, return
|
|
||||||
an empty bytes object.
|
|
||||||
|
|
||||||
If n is zero, return empty bytes object immediately.
|
|
||||||
|
|
||||||
If n is positive, this function try to read `n` bytes, and may return
|
|
||||||
less or equal bytes than requested, but at least one byte. If EOF was
|
|
||||||
received before any byte is read, this function returns empty byte
|
|
||||||
object.
|
|
||||||
|
|
||||||
Returned value is not limited with limit, configured at stream
|
|
||||||
creation.
|
|
||||||
|
|
||||||
If stream was paused, this function will automatically resume it if
|
|
||||||
needed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
|
|
||||||
if n == 0:
|
|
||||||
return b''
|
|
||||||
|
|
||||||
if n < 0:
|
|
||||||
# This used to just loop creating a new waiter hoping to
|
|
||||||
# collect everything in self._buffer, but that would
|
|
||||||
# deadlock if the subprocess sends more than self.limit
|
|
||||||
# bytes. So just call self.read(self._limit) until EOF.
|
|
||||||
blocks = []
|
|
||||||
while True:
|
|
||||||
block = yield from self.read(self._limit)
|
|
||||||
if not block:
|
|
||||||
break
|
|
||||||
blocks.append(block)
|
|
||||||
return b''.join(blocks)
|
|
||||||
|
|
||||||
if not self._buffer and not self._eof:
|
|
||||||
yield from self._wait_for_data('read')
|
|
||||||
|
|
||||||
# This will work right even if buffer is less than n bytes
|
|
||||||
data = bytes(self._buffer[:n])
|
|
||||||
del self._buffer[:n]
|
|
||||||
|
|
||||||
self._maybe_resume_transport()
|
|
||||||
return data
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def readexactly(self, n):
|
|
||||||
"""Read exactly `n` bytes.
|
|
||||||
|
|
||||||
Raise an IncompleteReadError if EOF is reached before `n` bytes can be
|
|
||||||
read. The IncompleteReadError.partial attribute of the exception will
|
|
||||||
contain the partial read bytes.
|
|
||||||
|
|
||||||
if n is zero, return empty bytes object.
|
|
||||||
|
|
||||||
Returned value is not limited with limit, configured at stream
|
|
||||||
creation.
|
|
||||||
|
|
||||||
If stream was paused, this function will automatically resume it if
|
|
||||||
needed.
|
|
||||||
"""
|
|
||||||
if n < 0:
|
|
||||||
raise ValueError('readexactly size can not be less than zero')
|
|
||||||
|
|
||||||
if self._exception is not None:
|
|
||||||
raise self._exception
|
|
||||||
|
|
||||||
if n == 0:
|
|
||||||
return b''
|
|
||||||
|
|
||||||
while len(self._buffer) < n:
|
|
||||||
if self._eof:
|
|
||||||
incomplete = bytes(self._buffer)
|
|
||||||
self._buffer.clear()
|
|
||||||
raise IncompleteReadError(incomplete, n)
|
|
||||||
|
|
||||||
yield from self._wait_for_data('readexactly')
|
|
||||||
|
|
||||||
if len(self._buffer) == n:
|
|
||||||
data = bytes(self._buffer)
|
|
||||||
self._buffer.clear()
|
|
||||||
else:
|
|
||||||
data = bytes(self._buffer[:n])
|
|
||||||
del self._buffer[:n]
|
|
||||||
self._maybe_resume_transport()
|
|
||||||
return data
|
|
||||||
|
|
||||||
if compat.PY35:
|
|
||||||
@coroutine
|
|
||||||
def __aiter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def __anext__(self):
|
|
||||||
val = yield from self.readline()
|
|
||||||
if val == b'':
|
|
||||||
raise StopAsyncIteration
|
|
||||||
return val
|
|
||||||
|
|
||||||
if compat.PY352:
|
|
||||||
# In Python 3.5.2 and greater, __aiter__ should return
|
|
||||||
# the asynchronous iterator directly.
|
|
||||||
def __aiter__(self):
|
|
||||||
return self
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
__all__ = ['create_subprocess_exec', 'create_subprocess_shell']
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from . import events
|
|
||||||
from . import protocols
|
|
||||||
from . import streams
|
|
||||||
from . import tasks
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
PIPE = subprocess.PIPE
|
|
||||||
STDOUT = subprocess.STDOUT
|
|
||||||
DEVNULL = subprocess.DEVNULL
|
|
||||||
|
|
||||||
|
|
||||||
class SubprocessStreamProtocol(streams.FlowControlMixin,
|
|
||||||
protocols.SubprocessProtocol):
|
|
||||||
"""Like StreamReaderProtocol, but for a subprocess."""
|
|
||||||
|
|
||||||
def __init__(self, limit, loop):
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
self._limit = limit
|
|
||||||
self.stdin = self.stdout = self.stderr = None
|
|
||||||
self._transport = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
info = [self.__class__.__name__]
|
|
||||||
if self.stdin is not None:
|
|
||||||
info.append('stdin=%r' % self.stdin)
|
|
||||||
if self.stdout is not None:
|
|
||||||
info.append('stdout=%r' % self.stdout)
|
|
||||||
if self.stderr is not None:
|
|
||||||
info.append('stderr=%r' % self.stderr)
|
|
||||||
return '<%s>' % ' '.join(info)
|
|
||||||
|
|
||||||
def connection_made(self, transport):
|
|
||||||
self._transport = transport
|
|
||||||
|
|
||||||
stdout_transport = transport.get_pipe_transport(1)
|
|
||||||
if stdout_transport is not None:
|
|
||||||
self.stdout = streams.StreamReader(limit=self._limit,
|
|
||||||
loop=self._loop)
|
|
||||||
self.stdout.set_transport(stdout_transport)
|
|
||||||
|
|
||||||
stderr_transport = transport.get_pipe_transport(2)
|
|
||||||
if stderr_transport is not None:
|
|
||||||
self.stderr = streams.StreamReader(limit=self._limit,
|
|
||||||
loop=self._loop)
|
|
||||||
self.stderr.set_transport(stderr_transport)
|
|
||||||
|
|
||||||
stdin_transport = transport.get_pipe_transport(0)
|
|
||||||
if stdin_transport is not None:
|
|
||||||
self.stdin = streams.StreamWriter(stdin_transport,
|
|
||||||
protocol=self,
|
|
||||||
reader=None,
|
|
||||||
loop=self._loop)
|
|
||||||
|
|
||||||
def pipe_data_received(self, fd, data):
|
|
||||||
if fd == 1:
|
|
||||||
reader = self.stdout
|
|
||||||
elif fd == 2:
|
|
||||||
reader = self.stderr
|
|
||||||
else:
|
|
||||||
reader = None
|
|
||||||
if reader is not None:
|
|
||||||
reader.feed_data(data)
|
|
||||||
|
|
||||||
def pipe_connection_lost(self, fd, exc):
|
|
||||||
if fd == 0:
|
|
||||||
pipe = self.stdin
|
|
||||||
if pipe is not None:
|
|
||||||
pipe.close()
|
|
||||||
self.connection_lost(exc)
|
|
||||||
return
|
|
||||||
if fd == 1:
|
|
||||||
reader = self.stdout
|
|
||||||
elif fd == 2:
|
|
||||||
reader = self.stderr
|
|
||||||
else:
|
|
||||||
reader = None
|
|
||||||
if reader != None:
|
|
||||||
if exc is None:
|
|
||||||
reader.feed_eof()
|
|
||||||
else:
|
|
||||||
reader.set_exception(exc)
|
|
||||||
|
|
||||||
def process_exited(self):
|
|
||||||
self._transport.close()
|
|
||||||
self._transport = None
|
|
||||||
|
|
||||||
|
|
||||||
class Process:
|
|
||||||
def __init__(self, transport, protocol, loop):
|
|
||||||
self._transport = transport
|
|
||||||
self._protocol = protocol
|
|
||||||
self._loop = loop
|
|
||||||
self.stdin = protocol.stdin
|
|
||||||
self.stdout = protocol.stdout
|
|
||||||
self.stderr = protocol.stderr
|
|
||||||
self.pid = transport.get_pid()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, self.pid)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def returncode(self):
|
|
||||||
return self._transport.get_returncode()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait(self):
|
|
||||||
"""Wait until the process exit and return the process return code.
|
|
||||||
|
|
||||||
This method is a coroutine."""
|
|
||||||
return (yield from self._transport._wait())
|
|
||||||
|
|
||||||
def send_signal(self, signal):
|
|
||||||
self._transport.send_signal(signal)
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
self._transport.terminate()
|
|
||||||
|
|
||||||
def kill(self):
|
|
||||||
self._transport.kill()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _feed_stdin(self, input):
|
|
||||||
debug = self._loop.get_debug()
|
|
||||||
self.stdin.write(input)
|
|
||||||
if debug:
|
|
||||||
logger.debug('%r communicate: feed stdin (%s bytes)',
|
|
||||||
self, len(input))
|
|
||||||
try:
|
|
||||||
yield from self.stdin.drain()
|
|
||||||
except (BrokenPipeError, ConnectionResetError) as exc:
|
|
||||||
# communicate() ignores BrokenPipeError and ConnectionResetError
|
|
||||||
if debug:
|
|
||||||
logger.debug('%r communicate: stdin got %r', self, exc)
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
logger.debug('%r communicate: close stdin', self)
|
|
||||||
self.stdin.close()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _noop(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _read_stream(self, fd):
|
|
||||||
transport = self._transport.get_pipe_transport(fd)
|
|
||||||
if fd == 2:
|
|
||||||
stream = self.stderr
|
|
||||||
else:
|
|
||||||
assert fd == 1
|
|
||||||
stream = self.stdout
|
|
||||||
if self._loop.get_debug():
|
|
||||||
name = 'stdout' if fd == 1 else 'stderr'
|
|
||||||
logger.debug('%r communicate: read %s', self, name)
|
|
||||||
output = yield from stream.read()
|
|
||||||
if self._loop.get_debug():
|
|
||||||
name = 'stdout' if fd == 1 else 'stderr'
|
|
||||||
logger.debug('%r communicate: close %s', self, name)
|
|
||||||
transport.close()
|
|
||||||
return output
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def communicate(self, input=None):
|
|
||||||
if input is not None:
|
|
||||||
stdin = self._feed_stdin(input)
|
|
||||||
else:
|
|
||||||
stdin = self._noop()
|
|
||||||
if self.stdout is not None:
|
|
||||||
stdout = self._read_stream(1)
|
|
||||||
else:
|
|
||||||
stdout = self._noop()
|
|
||||||
if self.stderr is not None:
|
|
||||||
stderr = self._read_stream(2)
|
|
||||||
else:
|
|
||||||
stderr = self._noop()
|
|
||||||
stdin, stdout, stderr = yield from tasks.gather(stdin, stdout, stderr,
|
|
||||||
loop=self._loop)
|
|
||||||
yield from self.wait()
|
|
||||||
return (stdout, stderr)
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None,
|
|
||||||
loop=None, limit=streams._DEFAULT_LIMIT, **kwds):
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
|
||||||
loop=loop)
|
|
||||||
transport, protocol = yield from loop.subprocess_shell(
|
|
||||||
protocol_factory,
|
|
||||||
cmd, stdin=stdin, stdout=stdout,
|
|
||||||
stderr=stderr, **kwds)
|
|
||||||
return Process(transport, protocol, loop)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def create_subprocess_exec(program, *args, stdin=None, stdout=None,
|
|
||||||
stderr=None, loop=None,
|
|
||||||
limit=streams._DEFAULT_LIMIT, **kwds):
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
protocol_factory = lambda: SubprocessStreamProtocol(limit=limit,
|
|
||||||
loop=loop)
|
|
||||||
transport, protocol = yield from loop.subprocess_exec(
|
|
||||||
protocol_factory,
|
|
||||||
program, *args,
|
|
||||||
stdin=stdin, stdout=stdout,
|
|
||||||
stderr=stderr, **kwds)
|
|
||||||
return Process(transport, protocol, loop)
|
|
||||||
@@ -1,807 +0,0 @@
|
|||||||
"""Support for tasks, coroutines and the scheduler."""
|
|
||||||
|
|
||||||
__all__ = ['Task', 'create_task',
|
|
||||||
'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED',
|
|
||||||
'wait', 'wait_for', 'as_completed', 'sleep', 'async',
|
|
||||||
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
|
|
||||||
]
|
|
||||||
|
|
||||||
import concurrent.futures
|
|
||||||
import functools
|
|
||||||
import inspect
|
|
||||||
import warnings
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from . import base_tasks
|
|
||||||
from . import compat
|
|
||||||
from . import coroutines
|
|
||||||
from . import events
|
|
||||||
from . import futures
|
|
||||||
from .coroutines import coroutine
|
|
||||||
|
|
||||||
|
|
||||||
def current_task(loop=None):
|
|
||||||
"""Return a currently executed task."""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_running_loop()
|
|
||||||
return _current_tasks.get(loop)
|
|
||||||
|
|
||||||
|
|
||||||
def all_tasks(loop=None):
|
|
||||||
"""Return a set of all tasks for the loop."""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_running_loop()
|
|
||||||
return {t for t in _all_tasks
|
|
||||||
if futures._get_loop(t) is loop and not t.done()}
|
|
||||||
|
|
||||||
|
|
||||||
def _all_tasks_compat(loop=None):
|
|
||||||
# Different from "all_task()" by returning *all* Tasks, including
|
|
||||||
# the completed ones. Used to implement deprecated "Tasks.all_task()"
|
|
||||||
# method.
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
return {t for t in _all_tasks if futures._get_loop(t) is loop}
|
|
||||||
|
|
||||||
|
|
||||||
def _set_task_name(task, name):
|
|
||||||
if name is not None:
|
|
||||||
try:
|
|
||||||
set_name = task.set_name
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
set_name(name)
|
|
||||||
|
|
||||||
|
|
||||||
class Task(futures.Future):
|
|
||||||
"""A coroutine wrapped in a Future."""
|
|
||||||
|
|
||||||
# An important invariant maintained while a Task not done:
|
|
||||||
#
|
|
||||||
# - Either _fut_waiter is None, and _step() is scheduled;
|
|
||||||
# - or _fut_waiter is some Future, and _step() is *not* scheduled.
|
|
||||||
#
|
|
||||||
# The only transition from the latter to the former is through
|
|
||||||
# _wakeup(). When _fut_waiter is not None, one of its callbacks
|
|
||||||
# must be _wakeup().
|
|
||||||
|
|
||||||
# Weak set containing all tasks alive.
|
|
||||||
_all_tasks = weakref.WeakSet()
|
|
||||||
|
|
||||||
# Dictionary containing tasks that are currently active in
|
|
||||||
# all running event loops. {EventLoop: Task}
|
|
||||||
_current_tasks = {}
|
|
||||||
|
|
||||||
# If False, don't log a message if the task is destroyed whereas its
|
|
||||||
# status is still pending
|
|
||||||
_log_destroy_pending = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def current_task(cls, loop=None):
|
|
||||||
"""Return the currently running task in an event loop or None.
|
|
||||||
|
|
||||||
By default the current task for the current event loop is returned.
|
|
||||||
|
|
||||||
None is returned when called not in the context of a Task.
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
return cls._current_tasks.get(loop)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def all_tasks(cls, loop=None):
|
|
||||||
"""Return a set of all tasks for an event loop.
|
|
||||||
|
|
||||||
By default all tasks for the current event loop are returned.
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
return {t for t in cls._all_tasks if t._loop is loop}
|
|
||||||
|
|
||||||
def __init__(self, coro, *, loop=None):
|
|
||||||
assert coroutines.iscoroutine(coro), repr(coro)
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
if self._source_traceback:
|
|
||||||
del self._source_traceback[-1]
|
|
||||||
self._coro = coro
|
|
||||||
self._fut_waiter = None
|
|
||||||
self._must_cancel = False
|
|
||||||
self._loop.call_soon(self._step)
|
|
||||||
self.__class__._all_tasks.add(self)
|
|
||||||
|
|
||||||
# On Python 3.3 or older, objects with a destructor that are part of a
|
|
||||||
# reference cycle are never destroyed. That's not the case any more on
|
|
||||||
# Python 3.4 thanks to the PEP 442.
|
|
||||||
if compat.PY34:
|
|
||||||
def __del__(self):
|
|
||||||
if self._state == futures._PENDING and self._log_destroy_pending:
|
|
||||||
context = {
|
|
||||||
'task': self,
|
|
||||||
'message': 'Task was destroyed but it is pending!',
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
futures.Future.__del__(self)
|
|
||||||
|
|
||||||
def _repr_info(self):
|
|
||||||
return base_tasks._task_repr_info(self)
|
|
||||||
|
|
||||||
def get_stack(self, *, limit=None):
|
|
||||||
"""Return the list of stack frames for this task's coroutine.
|
|
||||||
|
|
||||||
If the coroutine is not done, this returns the stack where it is
|
|
||||||
suspended. If the coroutine has completed successfully or was
|
|
||||||
cancelled, this returns an empty list. If the coroutine was
|
|
||||||
terminated by an exception, this returns the list of traceback
|
|
||||||
frames.
|
|
||||||
|
|
||||||
The frames are always ordered from oldest to newest.
|
|
||||||
|
|
||||||
The optional limit gives the maximum number of frames to
|
|
||||||
return; by default all available frames are returned. Its
|
|
||||||
meaning differs depending on whether a stack or a traceback is
|
|
||||||
returned: the newest frames of a stack are returned, but the
|
|
||||||
oldest frames of a traceback are returned. (This matches the
|
|
||||||
behavior of the traceback module.)
|
|
||||||
|
|
||||||
For reasons beyond our control, only one stack frame is
|
|
||||||
returned for a suspended coroutine.
|
|
||||||
"""
|
|
||||||
return base_tasks._task_get_stack(self, limit)
|
|
||||||
|
|
||||||
def print_stack(self, *, limit=None, file=None):
|
|
||||||
"""Print the stack or traceback for this task's coroutine.
|
|
||||||
|
|
||||||
This produces output similar to that of the traceback module,
|
|
||||||
for the frames retrieved by get_stack(). The limit argument
|
|
||||||
is passed to get_stack(). The file argument is an I/O stream
|
|
||||||
to which the output is written; by default output is written
|
|
||||||
to sys.stderr.
|
|
||||||
"""
|
|
||||||
return base_tasks._task_print_stack(self, limit, file)
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
"""Request that this task cancel itself.
|
|
||||||
|
|
||||||
This arranges for a CancelledError to be thrown into the
|
|
||||||
wrapped coroutine on the next cycle through the event loop.
|
|
||||||
The coroutine then has a chance to clean up or even deny
|
|
||||||
the request using try/except/finally.
|
|
||||||
|
|
||||||
Unlike Future.cancel, this does not guarantee that the
|
|
||||||
task will be cancelled: the exception might be caught and
|
|
||||||
acted upon, delaying cancellation of the task or preventing
|
|
||||||
cancellation completely. The task may also return a value or
|
|
||||||
raise a different exception.
|
|
||||||
|
|
||||||
Immediately after this method is called, Task.cancelled() will
|
|
||||||
not return True (unless the task was already cancelled). A
|
|
||||||
task will be marked as cancelled when the wrapped coroutine
|
|
||||||
terminates with a CancelledError exception (even if cancel()
|
|
||||||
was not called).
|
|
||||||
"""
|
|
||||||
if self.done():
|
|
||||||
return False
|
|
||||||
if self._fut_waiter is not None:
|
|
||||||
if self._fut_waiter.cancel():
|
|
||||||
# Leave self._fut_waiter; it may be a Task that
|
|
||||||
# catches and ignores the cancellation so we may have
|
|
||||||
# to cancel it again later.
|
|
||||||
return True
|
|
||||||
# It must be the case that self._step is already scheduled.
|
|
||||||
self._must_cancel = True
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _step(self, exc=None):
|
|
||||||
assert not self.done(), \
|
|
||||||
'_step(): already done: {!r}, {!r}'.format(self, exc)
|
|
||||||
if self._must_cancel:
|
|
||||||
if not isinstance(exc, futures.CancelledError):
|
|
||||||
exc = futures.CancelledError()
|
|
||||||
self._must_cancel = False
|
|
||||||
coro = self._coro
|
|
||||||
self._fut_waiter = None
|
|
||||||
|
|
||||||
self.__class__._current_tasks[self._loop] = self
|
|
||||||
# Call either coro.throw(exc) or coro.send(None).
|
|
||||||
try:
|
|
||||||
if exc is None:
|
|
||||||
# We use the `send` method directly, because coroutines
|
|
||||||
# don't have `__iter__` and `__next__` methods.
|
|
||||||
result = coro.send(None)
|
|
||||||
else:
|
|
||||||
result = coro.throw(exc)
|
|
||||||
except StopIteration as exc:
|
|
||||||
self.set_result(exc.value)
|
|
||||||
except futures.CancelledError:
|
|
||||||
super().cancel() # I.e., Future.cancel(self).
|
|
||||||
except Exception as exc:
|
|
||||||
self.set_exception(exc)
|
|
||||||
except BaseException as exc:
|
|
||||||
self.set_exception(exc)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
blocking = getattr(result, '_asyncio_future_blocking', None)
|
|
||||||
if blocking is not None:
|
|
||||||
# Yielded Future must come from Future.__iter__().
|
|
||||||
if result._loop is not self._loop:
|
|
||||||
self._loop.call_soon(
|
|
||||||
self._step,
|
|
||||||
RuntimeError(
|
|
||||||
'Task {!r} got Future {!r} attached to a '
|
|
||||||
'different loop'.format(self, result)))
|
|
||||||
elif blocking:
|
|
||||||
if result is self:
|
|
||||||
self._loop.call_soon(
|
|
||||||
self._step,
|
|
||||||
RuntimeError(
|
|
||||||
'Task cannot await on itself: {!r}'.format(
|
|
||||||
self)))
|
|
||||||
else:
|
|
||||||
result._asyncio_future_blocking = False
|
|
||||||
result.add_done_callback(self._wakeup)
|
|
||||||
self._fut_waiter = result
|
|
||||||
if self._must_cancel:
|
|
||||||
if self._fut_waiter.cancel():
|
|
||||||
self._must_cancel = False
|
|
||||||
else:
|
|
||||||
self._loop.call_soon(
|
|
||||||
self._step,
|
|
||||||
RuntimeError(
|
|
||||||
'yield was used instead of yield from '
|
|
||||||
'in task {!r} with {!r}'.format(self, result)))
|
|
||||||
elif result is None:
|
|
||||||
# Bare yield relinquishes control for one event loop iteration.
|
|
||||||
self._loop.call_soon(self._step)
|
|
||||||
elif inspect.isgenerator(result):
|
|
||||||
# Yielding a generator is just wrong.
|
|
||||||
self._loop.call_soon(
|
|
||||||
self._step,
|
|
||||||
RuntimeError(
|
|
||||||
'yield was used instead of yield from for '
|
|
||||||
'generator in task {!r} with {}'.format(
|
|
||||||
self, result)))
|
|
||||||
else:
|
|
||||||
# Yielding something else is an error.
|
|
||||||
self._loop.call_soon(
|
|
||||||
self._step,
|
|
||||||
RuntimeError(
|
|
||||||
'Task got bad yield: {!r}'.format(result)))
|
|
||||||
finally:
|
|
||||||
self.__class__._current_tasks.pop(self._loop)
|
|
||||||
self = None # Needed to break cycles when an exception occurs.
|
|
||||||
|
|
||||||
def _wakeup(self, future):
|
|
||||||
try:
|
|
||||||
future.result()
|
|
||||||
except Exception as exc:
|
|
||||||
# This may also be a cancellation.
|
|
||||||
self._step(exc)
|
|
||||||
else:
|
|
||||||
# Don't pass the value of `future.result()` explicitly,
|
|
||||||
# as `Future.__iter__` and `Future.__await__` don't need it.
|
|
||||||
# If we call `_step(value, None)` instead of `_step()`,
|
|
||||||
# Python eval loop would use `.send(value)` method call,
|
|
||||||
# instead of `__next__()`, which is slower for futures
|
|
||||||
# that return non-generator iterators from their `__iter__`.
|
|
||||||
self._step()
|
|
||||||
self = None # Needed to break cycles when an exception occurs.
|
|
||||||
|
|
||||||
|
|
||||||
_PyTask = Task
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
import _asyncio
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# _CTask is needed for tests.
|
|
||||||
Task = _CTask = _asyncio.Task
|
|
||||||
|
|
||||||
|
|
||||||
def create_task(coro, *, name=None):
|
|
||||||
"""Schedule the execution of a coroutine object in a spawn task.
|
|
||||||
|
|
||||||
Return a Task object.
|
|
||||||
"""
|
|
||||||
loop = events.get_running_loop()
|
|
||||||
task = loop.create_task(coro)
|
|
||||||
_set_task_name(task, name)
|
|
||||||
return task
|
|
||||||
|
|
||||||
|
|
||||||
# wait() and as_completed() similar to those in PEP 3148.
|
|
||||||
|
|
||||||
FIRST_COMPLETED = concurrent.futures.FIRST_COMPLETED
|
|
||||||
FIRST_EXCEPTION = concurrent.futures.FIRST_EXCEPTION
|
|
||||||
ALL_COMPLETED = concurrent.futures.ALL_COMPLETED
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
|
|
||||||
"""Wait for the Futures and coroutines given by fs to complete.
|
|
||||||
|
|
||||||
The sequence futures must not be empty.
|
|
||||||
|
|
||||||
Coroutines will be wrapped in Tasks.
|
|
||||||
|
|
||||||
Returns two sets of Future: (done, pending).
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
done, pending = yield from asyncio.wait(fs)
|
|
||||||
|
|
||||||
Note: This does not raise TimeoutError! Futures that aren't done
|
|
||||||
when the timeout occurs are returned in the second set.
|
|
||||||
"""
|
|
||||||
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
|
|
||||||
raise TypeError("expect a list of futures, not %s" % type(fs).__name__)
|
|
||||||
if not fs:
|
|
||||||
raise ValueError('Set of coroutines/Futures is empty.')
|
|
||||||
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
|
|
||||||
raise ValueError('Invalid return_when value: {}'.format(return_when))
|
|
||||||
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
fs = {ensure_future(f, loop=loop) for f in set(fs)}
|
|
||||||
|
|
||||||
return (yield from _wait(fs, timeout, return_when, loop))
|
|
||||||
|
|
||||||
|
|
||||||
def _release_waiter(waiter, *args):
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def wait_for(fut, timeout, *, loop=None):
|
|
||||||
"""Wait for the single Future or coroutine to complete, with timeout.
|
|
||||||
|
|
||||||
Coroutine will be wrapped in Task.
|
|
||||||
|
|
||||||
Returns result of the Future or coroutine. When a timeout occurs,
|
|
||||||
it cancels the task and raises TimeoutError. To avoid the task
|
|
||||||
cancellation, wrap it in shield().
|
|
||||||
|
|
||||||
If the wait is cancelled, the task is also cancelled.
|
|
||||||
|
|
||||||
This function is a coroutine.
|
|
||||||
"""
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
return (yield from fut)
|
|
||||||
|
|
||||||
waiter = loop.create_future()
|
|
||||||
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
|
|
||||||
cb = functools.partial(_release_waiter, waiter)
|
|
||||||
|
|
||||||
fut = ensure_future(fut, loop=loop)
|
|
||||||
fut.add_done_callback(cb)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# wait until the future completes or the timeout
|
|
||||||
try:
|
|
||||||
yield from waiter
|
|
||||||
except futures.CancelledError:
|
|
||||||
fut.remove_done_callback(cb)
|
|
||||||
fut.cancel()
|
|
||||||
raise
|
|
||||||
|
|
||||||
if fut.done():
|
|
||||||
return fut.result()
|
|
||||||
else:
|
|
||||||
fut.remove_done_callback(cb)
|
|
||||||
fut.cancel()
|
|
||||||
raise futures.TimeoutError()
|
|
||||||
finally:
|
|
||||||
timeout_handle.cancel()
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wait(fs, timeout, return_when, loop):
|
|
||||||
"""Internal helper for wait() and wait_for().
|
|
||||||
|
|
||||||
The fs argument must be a collection of Futures.
|
|
||||||
"""
|
|
||||||
assert fs, 'Set of Futures is empty.'
|
|
||||||
waiter = loop.create_future()
|
|
||||||
timeout_handle = None
|
|
||||||
if timeout is not None:
|
|
||||||
timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
|
|
||||||
counter = len(fs)
|
|
||||||
|
|
||||||
def _on_completion(f):
|
|
||||||
nonlocal counter
|
|
||||||
counter -= 1
|
|
||||||
if (counter <= 0 or
|
|
||||||
return_when == FIRST_COMPLETED or
|
|
||||||
return_when == FIRST_EXCEPTION and (not f.cancelled() and
|
|
||||||
f.exception() is not None)):
|
|
||||||
if timeout_handle is not None:
|
|
||||||
timeout_handle.cancel()
|
|
||||||
if not waiter.done():
|
|
||||||
waiter.set_result(None)
|
|
||||||
|
|
||||||
for f in fs:
|
|
||||||
f.add_done_callback(_on_completion)
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield from waiter
|
|
||||||
finally:
|
|
||||||
if timeout_handle is not None:
|
|
||||||
timeout_handle.cancel()
|
|
||||||
|
|
||||||
done, pending = set(), set()
|
|
||||||
for f in fs:
|
|
||||||
f.remove_done_callback(_on_completion)
|
|
||||||
if f.done():
|
|
||||||
done.add(f)
|
|
||||||
else:
|
|
||||||
pending.add(f)
|
|
||||||
return done, pending
|
|
||||||
|
|
||||||
|
|
||||||
# This is *not* a @coroutine! It is just an iterator (yielding Futures).
|
|
||||||
def as_completed(fs, *, loop=None, timeout=None):
|
|
||||||
"""Return an iterator whose values are coroutines.
|
|
||||||
|
|
||||||
When waiting for the yielded coroutines you'll get the results (or
|
|
||||||
exceptions!) of the original Futures (or coroutines), in the order
|
|
||||||
in which and as soon as they complete.
|
|
||||||
|
|
||||||
This differs from PEP 3148; the proper way to use this is:
|
|
||||||
|
|
||||||
for f in as_completed(fs):
|
|
||||||
result = yield from f # The 'yield from' may raise.
|
|
||||||
# Use result.
|
|
||||||
|
|
||||||
If a timeout is specified, the 'yield from' will raise
|
|
||||||
TimeoutError when the timeout occurs before all Futures are done.
|
|
||||||
|
|
||||||
Note: The futures 'f' are not necessarily members of fs.
|
|
||||||
"""
|
|
||||||
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
|
|
||||||
raise TypeError("expect a list of futures, not %s" % type(fs).__name__)
|
|
||||||
loop = loop if loop is not None else events.get_event_loop()
|
|
||||||
todo = {ensure_future(f, loop=loop) for f in set(fs)}
|
|
||||||
from .queues import Queue # Import here to avoid circular import problem.
|
|
||||||
done = Queue(loop=loop)
|
|
||||||
timeout_handle = None
|
|
||||||
|
|
||||||
def _on_timeout():
|
|
||||||
for f in todo:
|
|
||||||
f.remove_done_callback(_on_completion)
|
|
||||||
done.put_nowait(None) # Queue a dummy value for _wait_for_one().
|
|
||||||
todo.clear() # Can't do todo.remove(f) in the loop.
|
|
||||||
|
|
||||||
def _on_completion(f):
|
|
||||||
if not todo:
|
|
||||||
return # _on_timeout() was here first.
|
|
||||||
todo.remove(f)
|
|
||||||
done.put_nowait(f)
|
|
||||||
if not todo and timeout_handle is not None:
|
|
||||||
timeout_handle.cancel()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wait_for_one():
|
|
||||||
f = yield from done.get()
|
|
||||||
if f is None:
|
|
||||||
# Dummy value from _on_timeout().
|
|
||||||
raise futures.TimeoutError
|
|
||||||
return f.result() # May raise f.exception().
|
|
||||||
|
|
||||||
for f in todo:
|
|
||||||
f.add_done_callback(_on_completion)
|
|
||||||
if todo and timeout is not None:
|
|
||||||
timeout_handle = loop.call_later(timeout, _on_timeout)
|
|
||||||
for _ in range(len(todo)):
|
|
||||||
yield _wait_for_one()
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def sleep(delay, result=None, *, loop=None):
|
|
||||||
"""Coroutine that completes after a given time (in seconds)."""
|
|
||||||
if delay == 0:
|
|
||||||
yield
|
|
||||||
return result
|
|
||||||
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
future = loop.create_future()
|
|
||||||
h = future._loop.call_later(delay,
|
|
||||||
futures._set_result_unless_cancelled,
|
|
||||||
future, result)
|
|
||||||
try:
|
|
||||||
return (yield from future)
|
|
||||||
finally:
|
|
||||||
h.cancel()
|
|
||||||
|
|
||||||
|
|
||||||
def async_(coro_or_future, *, loop=None):
|
|
||||||
"""Wrap a coroutine in a future.
|
|
||||||
|
|
||||||
If the argument is a Future, it is returned directly.
|
|
||||||
|
|
||||||
This function is deprecated in 3.5. Use asyncio.ensure_future() instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
warnings.warn("asyncio.async() function is deprecated, use ensure_future()",
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
return ensure_future(coro_or_future, loop=loop)
|
|
||||||
|
|
||||||
# Silence DeprecationWarning:
|
|
||||||
globals()['async'] = async_
|
|
||||||
async_.__name__ = 'async'
|
|
||||||
del async_
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_future(coro_or_future, *, loop=None):
|
|
||||||
"""Wrap a coroutine or an awaitable in a future.
|
|
||||||
|
|
||||||
If the argument is a Future, it is returned directly.
|
|
||||||
"""
|
|
||||||
if futures.isfuture(coro_or_future):
|
|
||||||
if loop is not None and loop is not coro_or_future._loop:
|
|
||||||
raise ValueError('loop argument must agree with Future')
|
|
||||||
return coro_or_future
|
|
||||||
elif coroutines.iscoroutine(coro_or_future):
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
task = loop.create_task(coro_or_future)
|
|
||||||
if task._source_traceback:
|
|
||||||
del task._source_traceback[-1]
|
|
||||||
return task
|
|
||||||
elif compat.PY35 and inspect.isawaitable(coro_or_future):
|
|
||||||
return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)
|
|
||||||
else:
|
|
||||||
raise TypeError('A Future, a coroutine or an awaitable is required')
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _wrap_awaitable(awaitable):
|
|
||||||
"""Helper for asyncio.ensure_future().
|
|
||||||
|
|
||||||
Wraps awaitable (an object with __await__) into a coroutine
|
|
||||||
that will later be wrapped in a Task by ensure_future().
|
|
||||||
"""
|
|
||||||
return (yield from awaitable.__await__())
|
|
||||||
|
|
||||||
|
|
||||||
class _GatheringFuture(futures.Future):
|
|
||||||
"""Helper for gather().
|
|
||||||
|
|
||||||
This overrides cancel() to cancel all the children and act more
|
|
||||||
like Task.cancel(), which doesn't immediately mark itself as
|
|
||||||
cancelled.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, children, *, loop=None):
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
self._children = children
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
if self.done():
|
|
||||||
return False
|
|
||||||
ret = False
|
|
||||||
for child in self._children:
|
|
||||||
if child.cancel():
|
|
||||||
ret = True
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def gather(*coros_or_futures, loop=None, return_exceptions=False):
|
|
||||||
"""Return a future aggregating results from the given coroutines
|
|
||||||
or futures.
|
|
||||||
|
|
||||||
Coroutines will be wrapped in a future and scheduled in the event
|
|
||||||
loop. They will not necessarily be scheduled in the same order as
|
|
||||||
passed in.
|
|
||||||
|
|
||||||
All futures must share the same event loop. If all the tasks are
|
|
||||||
done successfully, the returned future's result is the list of
|
|
||||||
results (in the order of the original sequence, not necessarily
|
|
||||||
the order of results arrival). If *return_exceptions* is True,
|
|
||||||
exceptions in the tasks are treated the same as successful
|
|
||||||
results, and gathered in the result list; otherwise, the first
|
|
||||||
raised exception will be immediately propagated to the returned
|
|
||||||
future.
|
|
||||||
|
|
||||||
Cancellation: if the outer Future is cancelled, all children (that
|
|
||||||
have not completed yet) are also cancelled. If any child is
|
|
||||||
cancelled, this is treated as if it raised CancelledError --
|
|
||||||
the outer Future is *not* cancelled in this case. (This is to
|
|
||||||
prevent the cancellation of one child to cause other children to
|
|
||||||
be cancelled.)
|
|
||||||
"""
|
|
||||||
if not coros_or_futures:
|
|
||||||
if loop is None:
|
|
||||||
loop = events.get_event_loop()
|
|
||||||
outer = loop.create_future()
|
|
||||||
outer.set_result([])
|
|
||||||
return outer
|
|
||||||
|
|
||||||
arg_to_fut = {}
|
|
||||||
for arg in set(coros_or_futures):
|
|
||||||
if not futures.isfuture(arg):
|
|
||||||
fut = ensure_future(arg, loop=loop)
|
|
||||||
if loop is None:
|
|
||||||
loop = fut._loop
|
|
||||||
# The caller cannot control this future, the "destroy pending task"
|
|
||||||
# warning should not be emitted.
|
|
||||||
fut._log_destroy_pending = False
|
|
||||||
else:
|
|
||||||
fut = arg
|
|
||||||
if loop is None:
|
|
||||||
loop = fut._loop
|
|
||||||
elif fut._loop is not loop:
|
|
||||||
raise ValueError("futures are tied to different event loops")
|
|
||||||
arg_to_fut[arg] = fut
|
|
||||||
|
|
||||||
children = [arg_to_fut[arg] for arg in coros_or_futures]
|
|
||||||
nchildren = len(children)
|
|
||||||
outer = _GatheringFuture(children, loop=loop)
|
|
||||||
nfinished = 0
|
|
||||||
results = [None] * nchildren
|
|
||||||
|
|
||||||
def _done_callback(i, fut):
|
|
||||||
nonlocal nfinished
|
|
||||||
if outer.done():
|
|
||||||
if not fut.cancelled():
|
|
||||||
# Mark exception retrieved.
|
|
||||||
fut.exception()
|
|
||||||
return
|
|
||||||
|
|
||||||
if fut.cancelled():
|
|
||||||
res = futures.CancelledError()
|
|
||||||
if not return_exceptions:
|
|
||||||
outer.set_exception(res)
|
|
||||||
return
|
|
||||||
elif fut._exception is not None:
|
|
||||||
res = fut.exception() # Mark exception retrieved.
|
|
||||||
if not return_exceptions:
|
|
||||||
outer.set_exception(res)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
res = fut._result
|
|
||||||
results[i] = res
|
|
||||||
nfinished += 1
|
|
||||||
if nfinished == nchildren:
|
|
||||||
outer.set_result(results)
|
|
||||||
|
|
||||||
for i, fut in enumerate(children):
|
|
||||||
fut.add_done_callback(functools.partial(_done_callback, i))
|
|
||||||
return outer
|
|
||||||
|
|
||||||
|
|
||||||
def shield(arg, *, loop=None):
|
|
||||||
"""Wait for a future, shielding it from cancellation.
|
|
||||||
|
|
||||||
The statement
|
|
||||||
|
|
||||||
res = yield from shield(something())
|
|
||||||
|
|
||||||
is exactly equivalent to the statement
|
|
||||||
|
|
||||||
res = yield from something()
|
|
||||||
|
|
||||||
*except* that if the coroutine containing it is cancelled, the
|
|
||||||
task running in something() is not cancelled. From the POV of
|
|
||||||
something(), the cancellation did not happen. But its caller is
|
|
||||||
still cancelled, so the yield-from expression still raises
|
|
||||||
CancelledError. Note: If something() is cancelled by other means
|
|
||||||
this will still cancel shield().
|
|
||||||
|
|
||||||
If you want to completely ignore cancellation (not recommended)
|
|
||||||
you can combine shield() with a try/except clause, as follows:
|
|
||||||
|
|
||||||
try:
|
|
||||||
res = yield from shield(something())
|
|
||||||
except CancelledError:
|
|
||||||
res = None
|
|
||||||
"""
|
|
||||||
inner = ensure_future(arg, loop=loop)
|
|
||||||
if inner.done():
|
|
||||||
# Shortcut.
|
|
||||||
return inner
|
|
||||||
loop = inner._loop
|
|
||||||
outer = loop.create_future()
|
|
||||||
|
|
||||||
def _done_callback(inner):
|
|
||||||
if outer.cancelled():
|
|
||||||
if not inner.cancelled():
|
|
||||||
# Mark inner's result as retrieved.
|
|
||||||
inner.exception()
|
|
||||||
return
|
|
||||||
|
|
||||||
if inner.cancelled():
|
|
||||||
outer.cancel()
|
|
||||||
else:
|
|
||||||
exc = inner.exception()
|
|
||||||
if exc is not None:
|
|
||||||
outer.set_exception(exc)
|
|
||||||
else:
|
|
||||||
outer.set_result(inner.result())
|
|
||||||
|
|
||||||
inner.add_done_callback(_done_callback)
|
|
||||||
return outer
|
|
||||||
|
|
||||||
|
|
||||||
def run_coroutine_threadsafe(coro, loop):
|
|
||||||
"""Submit a coroutine object to a given event loop.
|
|
||||||
|
|
||||||
Return a concurrent.futures.Future to access the result.
|
|
||||||
"""
|
|
||||||
if not coroutines.iscoroutine(coro):
|
|
||||||
raise TypeError('A coroutine object is required')
|
|
||||||
future = concurrent.futures.Future()
|
|
||||||
|
|
||||||
def callback():
|
|
||||||
try:
|
|
||||||
futures._chain_future(ensure_future(coro, loop=loop), future)
|
|
||||||
except Exception as exc:
|
|
||||||
if future.set_running_or_notify_cancel():
|
|
||||||
future.set_exception(exc)
|
|
||||||
raise
|
|
||||||
|
|
||||||
loop.call_soon_threadsafe(callback)
|
|
||||||
return future
|
|
||||||
|
|
||||||
|
|
||||||
# WeakSet containing all alive tasks.
|
|
||||||
_all_tasks = weakref.WeakSet()
|
|
||||||
|
|
||||||
# Dictionary containing tasks that are currently active in
|
|
||||||
# all running event loops. {EventLoop: Task}
|
|
||||||
_current_tasks = {}
|
|
||||||
|
|
||||||
|
|
||||||
def _register_task(task):
|
|
||||||
"""Register a new task in asyncio as executed by loop."""
|
|
||||||
_all_tasks.add(task)
|
|
||||||
|
|
||||||
|
|
||||||
def _enter_task(loop, task):
|
|
||||||
current_task = _current_tasks.get(loop)
|
|
||||||
if current_task is not None:
|
|
||||||
raise RuntimeError(f"Cannot enter into task {task!r} while another "
|
|
||||||
f"task {current_task!r} is being executed.")
|
|
||||||
_current_tasks[loop] = task
|
|
||||||
|
|
||||||
|
|
||||||
def _leave_task(loop, task):
|
|
||||||
current_task = _current_tasks.get(loop)
|
|
||||||
if current_task is not task:
|
|
||||||
raise RuntimeError(f"Leaving task {task!r} does not match "
|
|
||||||
f"the current task {current_task!r}.")
|
|
||||||
del _current_tasks[loop]
|
|
||||||
|
|
||||||
|
|
||||||
def _unregister_task(task):
|
|
||||||
"""Unregister a task."""
|
|
||||||
_all_tasks.discard(task)
|
|
||||||
|
|
||||||
|
|
||||||
_py_register_task = _register_task
|
|
||||||
_py_unregister_task = _unregister_task
|
|
||||||
_py_enter_task = _enter_task
|
|
||||||
_py_leave_task = _leave_task
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from _asyncio import (_register_task, _unregister_task,
|
|
||||||
_enter_task, _leave_task,
|
|
||||||
_all_tasks, _current_tasks)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
_c_register_task = _register_task
|
|
||||||
_c_unregister_task = _unregister_task
|
|
||||||
_c_enter_task = _enter_task
|
|
||||||
_c_leave_task = _leave_task
|
|
||||||
@@ -1,503 +0,0 @@
|
|||||||
"""Utilities shared by tests."""
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import socket
|
|
||||||
import socketserver
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from http.server import HTTPServer
|
|
||||||
from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
|
|
||||||
|
|
||||||
try:
|
|
||||||
import ssl
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
ssl = None
|
|
||||||
|
|
||||||
from . import base_events
|
|
||||||
from . import compat
|
|
||||||
from . import events
|
|
||||||
from . import futures
|
|
||||||
from . import selectors
|
|
||||||
from . import tasks
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == 'win32': # pragma: no cover
|
|
||||||
from .windows_utils import socketpair
|
|
||||||
else:
|
|
||||||
from socket import socketpair # pragma: no cover
|
|
||||||
|
|
||||||
|
|
||||||
def dummy_ssl_context():
|
|
||||||
if ssl is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
|
|
||||||
|
|
||||||
def run_briefly(loop):
|
|
||||||
@coroutine
|
|
||||||
def once():
|
|
||||||
pass
|
|
||||||
gen = once()
|
|
||||||
t = loop.create_task(gen)
|
|
||||||
# Don't log a warning if the task is not done after run_until_complete().
|
|
||||||
# It occurs if the loop is stopped or if a task raises a BaseException.
|
|
||||||
t._log_destroy_pending = False
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(t)
|
|
||||||
finally:
|
|
||||||
gen.close()
|
|
||||||
|
|
||||||
|
|
||||||
def run_until(loop, pred, timeout=30):
|
|
||||||
deadline = time.time() + timeout
|
|
||||||
while not pred():
|
|
||||||
if timeout is not None:
|
|
||||||
timeout = deadline - time.time()
|
|
||||||
if timeout <= 0:
|
|
||||||
raise futures.TimeoutError()
|
|
||||||
loop.run_until_complete(tasks.sleep(0.001, loop=loop))
|
|
||||||
|
|
||||||
|
|
||||||
def run_once(loop):
|
|
||||||
"""Legacy API to run once through the event loop.
|
|
||||||
|
|
||||||
This is the recommended pattern for test code. It will poll the
|
|
||||||
selector once and run all callbacks scheduled in response to I/O
|
|
||||||
events.
|
|
||||||
"""
|
|
||||||
loop.call_soon(loop.stop)
|
|
||||||
loop.run_forever()
|
|
||||||
|
|
||||||
|
|
||||||
class SilentWSGIRequestHandler(WSGIRequestHandler):
|
|
||||||
|
|
||||||
def get_stderr(self):
|
|
||||||
return io.StringIO()
|
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SilentWSGIServer(WSGIServer):
|
|
||||||
|
|
||||||
request_timeout = 2
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
request, client_addr = super().get_request()
|
|
||||||
request.settimeout(self.request_timeout)
|
|
||||||
return request, client_addr
|
|
||||||
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLWSGIServerMixin:
|
|
||||||
|
|
||||||
def finish_request(self, request, client_address):
|
|
||||||
# The relative location of our test directory (which
|
|
||||||
# contains the ssl key and certificate files) differs
|
|
||||||
# between the stdlib and stand-alone asyncio.
|
|
||||||
# Prefer our own if we can find it.
|
|
||||||
here = os.path.join(os.path.dirname(__file__), '..', 'tests')
|
|
||||||
if not os.path.isdir(here):
|
|
||||||
here = os.path.join(os.path.dirname(os.__file__),
|
|
||||||
'test', 'test_asyncio')
|
|
||||||
keyfile = os.path.join(here, 'ssl_key.pem')
|
|
||||||
certfile = os.path.join(here, 'ssl_cert.pem')
|
|
||||||
context = ssl.SSLContext()
|
|
||||||
context.load_cert_chain(certfile, keyfile)
|
|
||||||
|
|
||||||
ssock = context.wrap_socket(request, server_side=True)
|
|
||||||
try:
|
|
||||||
self.RequestHandlerClass(ssock, client_address, self)
|
|
||||||
ssock.close()
|
|
||||||
except OSError:
|
|
||||||
# maybe socket has been closed by peer
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SSLWSGIServer(SSLWSGIServerMixin, SilentWSGIServer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _run_test_server(*, address, use_ssl=False, server_cls, server_ssl_cls):
|
|
||||||
|
|
||||||
def app(environ, start_response):
|
|
||||||
status = '200 OK'
|
|
||||||
headers = [('Content-type', 'text/plain')]
|
|
||||||
start_response(status, headers)
|
|
||||||
return [b'Test message']
|
|
||||||
|
|
||||||
# Run the test WSGI server in a separate thread in order not to
|
|
||||||
# interfere with event handling in the main thread
|
|
||||||
server_class = server_ssl_cls if use_ssl else server_cls
|
|
||||||
httpd = server_class(address, SilentWSGIRequestHandler)
|
|
||||||
httpd.set_app(app)
|
|
||||||
httpd.address = httpd.server_address
|
|
||||||
server_thread = threading.Thread(
|
|
||||||
target=lambda: httpd.serve_forever(poll_interval=0.05))
|
|
||||||
server_thread.start()
|
|
||||||
try:
|
|
||||||
yield httpd
|
|
||||||
finally:
|
|
||||||
httpd.shutdown()
|
|
||||||
httpd.server_close()
|
|
||||||
server_thread.join()
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'AF_UNIX'):
|
|
||||||
|
|
||||||
class UnixHTTPServer(socketserver.UnixStreamServer, HTTPServer):
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
socketserver.UnixStreamServer.server_bind(self)
|
|
||||||
self.server_name = '127.0.0.1'
|
|
||||||
self.server_port = 80
|
|
||||||
|
|
||||||
|
|
||||||
class UnixWSGIServer(UnixHTTPServer, WSGIServer):
|
|
||||||
|
|
||||||
request_timeout = 2
|
|
||||||
|
|
||||||
def server_bind(self):
|
|
||||||
UnixHTTPServer.server_bind(self)
|
|
||||||
self.setup_environ()
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
request, client_addr = super().get_request()
|
|
||||||
request.settimeout(self.request_timeout)
|
|
||||||
# Code in the stdlib expects that get_request
|
|
||||||
# will return a socket and a tuple (host, port).
|
|
||||||
# However, this isn't true for UNIX sockets,
|
|
||||||
# as the second return value will be a path;
|
|
||||||
# hence we return some fake data sufficient
|
|
||||||
# to get the tests going
|
|
||||||
return request, ('127.0.0.1', '')
|
|
||||||
|
|
||||||
|
|
||||||
class SilentUnixWSGIServer(UnixWSGIServer):
|
|
||||||
|
|
||||||
def handle_error(self, request, client_address):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnixSSLWSGIServer(SSLWSGIServerMixin, SilentUnixWSGIServer):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def gen_unix_socket_path():
|
|
||||||
with tempfile.NamedTemporaryFile() as file:
|
|
||||||
return file.name
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def unix_socket_path():
|
|
||||||
path = gen_unix_socket_path()
|
|
||||||
try:
|
|
||||||
yield path
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
os.unlink(path)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def run_test_unix_server(*, use_ssl=False):
|
|
||||||
with unix_socket_path() as path:
|
|
||||||
yield from _run_test_server(address=path, use_ssl=use_ssl,
|
|
||||||
server_cls=SilentUnixWSGIServer,
|
|
||||||
server_ssl_cls=UnixSSLWSGIServer)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def run_test_server(*, host='127.0.0.1', port=0, use_ssl=False):
|
|
||||||
yield from _run_test_server(address=(host, port), use_ssl=use_ssl,
|
|
||||||
server_cls=SilentWSGIServer,
|
|
||||||
server_ssl_cls=SSLWSGIServer)
|
|
||||||
|
|
||||||
|
|
||||||
def make_test_protocol(base):
|
|
||||||
dct = {}
|
|
||||||
for name in dir(base):
|
|
||||||
if name.startswith('__') and name.endswith('__'):
|
|
||||||
# skip magic names
|
|
||||||
continue
|
|
||||||
dct[name] = MockCallback(return_value=None)
|
|
||||||
return type('TestProtocol', (base,) + base.__bases__, dct)()
|
|
||||||
|
|
||||||
|
|
||||||
class TestSelector(selectors.BaseSelector):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.keys = {}
|
|
||||||
|
|
||||||
def register(self, fileobj, events, data=None):
|
|
||||||
key = selectors.SelectorKey(fileobj, 0, events, data)
|
|
||||||
self.keys[fileobj] = key
|
|
||||||
return key
|
|
||||||
|
|
||||||
def unregister(self, fileobj):
|
|
||||||
return self.keys.pop(fileobj)
|
|
||||||
|
|
||||||
def select(self, timeout):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_map(self):
|
|
||||||
return self.keys
|
|
||||||
|
|
||||||
|
|
||||||
class TestLoop(base_events.BaseEventLoop):
|
|
||||||
"""Loop for unittests.
|
|
||||||
|
|
||||||
It manages self time directly.
|
|
||||||
If something scheduled to be executed later then
|
|
||||||
on next loop iteration after all ready handlers done
|
|
||||||
generator passed to __init__ is calling.
|
|
||||||
|
|
||||||
Generator should be like this:
|
|
||||||
|
|
||||||
def gen():
|
|
||||||
...
|
|
||||||
when = yield ...
|
|
||||||
... = yield time_advance
|
|
||||||
|
|
||||||
Value returned by yield is absolute time of next scheduled handler.
|
|
||||||
Value passed to yield is time advance to move loop's time forward.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, gen=None):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
if gen is None:
|
|
||||||
def gen():
|
|
||||||
yield
|
|
||||||
self._check_on_close = False
|
|
||||||
else:
|
|
||||||
self._check_on_close = True
|
|
||||||
|
|
||||||
self._gen = gen()
|
|
||||||
next(self._gen)
|
|
||||||
self._time = 0
|
|
||||||
self._clock_resolution = 1e-9
|
|
||||||
self._timers = []
|
|
||||||
self._selector = TestSelector()
|
|
||||||
|
|
||||||
self.readers = {}
|
|
||||||
self.writers = {}
|
|
||||||
self.reset_counters()
|
|
||||||
|
|
||||||
self._transports = weakref.WeakValueDictionary()
|
|
||||||
|
|
||||||
def time(self):
|
|
||||||
return self._time
|
|
||||||
|
|
||||||
def advance_time(self, advance):
|
|
||||||
"""Move test time forward."""
|
|
||||||
if advance:
|
|
||||||
self._time += advance
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
super().close()
|
|
||||||
if self._check_on_close:
|
|
||||||
try:
|
|
||||||
self._gen.send(0)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
else: # pragma: no cover
|
|
||||||
raise AssertionError("Time generator is not finished")
|
|
||||||
|
|
||||||
def _add_reader(self, fd, callback, *args):
|
|
||||||
self.readers[fd] = events.Handle(callback, args, self)
|
|
||||||
|
|
||||||
def _remove_reader(self, fd):
|
|
||||||
self.remove_reader_count[fd] += 1
|
|
||||||
if fd in self.readers:
|
|
||||||
del self.readers[fd]
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def assert_reader(self, fd, callback, *args):
|
|
||||||
assert fd in self.readers, 'fd {} is not registered'.format(fd)
|
|
||||||
handle = self.readers[fd]
|
|
||||||
assert handle._callback == callback, '{!r} != {!r}'.format(
|
|
||||||
handle._callback, callback)
|
|
||||||
assert handle._args == args, '{!r} != {!r}'.format(
|
|
||||||
handle._args, args)
|
|
||||||
|
|
||||||
def _add_writer(self, fd, callback, *args):
|
|
||||||
self.writers[fd] = events.Handle(callback, args, self)
|
|
||||||
|
|
||||||
def _remove_writer(self, fd):
|
|
||||||
self.remove_writer_count[fd] += 1
|
|
||||||
if fd in self.writers:
|
|
||||||
del self.writers[fd]
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def assert_writer(self, fd, callback, *args):
|
|
||||||
assert fd in self.writers, 'fd {} is not registered'.format(fd)
|
|
||||||
handle = self.writers[fd]
|
|
||||||
assert handle._callback == callback, '{!r} != {!r}'.format(
|
|
||||||
handle._callback, callback)
|
|
||||||
assert handle._args == args, '{!r} != {!r}'.format(
|
|
||||||
handle._args, args)
|
|
||||||
|
|
||||||
def _ensure_fd_no_transport(self, fd):
|
|
||||||
try:
|
|
||||||
transport = self._transports[fd]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise RuntimeError(
|
|
||||||
'File descriptor {!r} is used by transport {!r}'.format(
|
|
||||||
fd, transport))
|
|
||||||
|
|
||||||
def add_reader(self, fd, callback, *args):
|
|
||||||
"""Add a reader callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._add_reader(fd, callback, *args)
|
|
||||||
|
|
||||||
def remove_reader(self, fd):
|
|
||||||
"""Remove a reader callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._remove_reader(fd)
|
|
||||||
|
|
||||||
def add_writer(self, fd, callback, *args):
|
|
||||||
"""Add a writer callback.."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._add_writer(fd, callback, *args)
|
|
||||||
|
|
||||||
def remove_writer(self, fd):
|
|
||||||
"""Remove a writer callback."""
|
|
||||||
self._ensure_fd_no_transport(fd)
|
|
||||||
return self._remove_writer(fd)
|
|
||||||
|
|
||||||
def reset_counters(self):
|
|
||||||
self.remove_reader_count = collections.defaultdict(int)
|
|
||||||
self.remove_writer_count = collections.defaultdict(int)
|
|
||||||
|
|
||||||
def _run_once(self):
|
|
||||||
super()._run_once()
|
|
||||||
for when in self._timers:
|
|
||||||
advance = self._gen.send(when)
|
|
||||||
self.advance_time(advance)
|
|
||||||
self._timers = []
|
|
||||||
|
|
||||||
def call_at(self, when, callback, *args):
|
|
||||||
self._timers.append(when)
|
|
||||||
return super().call_at(when, callback, *args)
|
|
||||||
|
|
||||||
def _process_events(self, event_list):
|
|
||||||
return
|
|
||||||
|
|
||||||
def _write_to_self(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def MockCallback(**kwargs):
|
|
||||||
return mock.Mock(spec=['__call__'], **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MockPattern(str):
|
|
||||||
"""A regex based str with a fuzzy __eq__.
|
|
||||||
|
|
||||||
Use this helper with 'mock.assert_called_with', or anywhere
|
|
||||||
where a regex comparison between strings is needed.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
mock_call.assert_called_with(MockPattern('spam.*ham'))
|
|
||||||
"""
|
|
||||||
def __eq__(self, other):
|
|
||||||
return bool(re.search(str(self), other, re.S))
|
|
||||||
|
|
||||||
|
|
||||||
def get_function_source(func):
|
|
||||||
source = events._get_function_source(func)
|
|
||||||
if source is None:
|
|
||||||
raise ValueError("unable to get the source of %r" % (func,))
|
|
||||||
return source
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
|
||||||
def set_event_loop(self, loop, *, cleanup=True):
|
|
||||||
assert loop is not None
|
|
||||||
# ensure that the event loop is passed explicitly in asyncio
|
|
||||||
events.set_event_loop(None)
|
|
||||||
if cleanup:
|
|
||||||
self.addCleanup(loop.close)
|
|
||||||
|
|
||||||
def new_test_loop(self, gen=None):
|
|
||||||
loop = TestLoop(gen)
|
|
||||||
self.set_event_loop(loop)
|
|
||||||
return loop
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self._get_running_loop = events._get_running_loop
|
|
||||||
events._get_running_loop = lambda: None
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
events._get_running_loop = self._get_running_loop
|
|
||||||
|
|
||||||
events.set_event_loop(None)
|
|
||||||
|
|
||||||
# Detect CPython bug #23353: ensure that yield/yield-from is not used
|
|
||||||
# in an except block of a generator
|
|
||||||
self.assertEqual(sys.exc_info(), (None, None, None))
|
|
||||||
|
|
||||||
if not compat.PY34:
|
|
||||||
# Python 3.3 compatibility
|
|
||||||
def subTest(self, *args, **kwargs):
|
|
||||||
class EmptyCM:
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
pass
|
|
||||||
return EmptyCM()
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def disable_logger():
|
|
||||||
"""Context manager to disable asyncio logger.
|
|
||||||
|
|
||||||
For example, it can be used to ignore warnings in debug mode.
|
|
||||||
"""
|
|
||||||
old_level = logger.level
|
|
||||||
try:
|
|
||||||
logger.setLevel(logging.CRITICAL+1)
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
logger.setLevel(old_level)
|
|
||||||
|
|
||||||
|
|
||||||
def mock_nonblocking_socket(proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM,
|
|
||||||
family=socket.AF_INET):
|
|
||||||
"""Create a mock of a non-blocking socket."""
|
|
||||||
sock = mock.MagicMock(socket.socket)
|
|
||||||
sock.proto = proto
|
|
||||||
sock.type = type
|
|
||||||
sock.family = family
|
|
||||||
sock.gettimeout.return_value = 0.0
|
|
||||||
return sock
|
|
||||||
|
|
||||||
|
|
||||||
def force_legacy_ssl_support():
|
|
||||||
return mock.patch('asyncio.sslproto._is_sslproto_available',
|
|
||||||
return_value=False)
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
"""Abstract Transport class."""
|
|
||||||
|
|
||||||
from asyncio import compat
|
|
||||||
|
|
||||||
__all__ = ['BaseTransport', 'ReadTransport', 'WriteTransport',
|
|
||||||
'Transport', 'DatagramTransport', 'SubprocessTransport',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTransport:
|
|
||||||
"""Base class for transports."""
|
|
||||||
|
|
||||||
def __init__(self, extra=None):
|
|
||||||
if extra is None:
|
|
||||||
extra = {}
|
|
||||||
self._extra = extra
|
|
||||||
|
|
||||||
def get_extra_info(self, name, default=None):
|
|
||||||
"""Get optional transport information."""
|
|
||||||
return self._extra.get(name, default)
|
|
||||||
|
|
||||||
def is_closing(self):
|
|
||||||
"""Return True if the transport is closing or closed."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Close the transport.
|
|
||||||
|
|
||||||
Buffered data will be flushed asynchronously. No more data
|
|
||||||
will be received. After all buffered data is flushed, the
|
|
||||||
protocol's connection_lost() method will (eventually) called
|
|
||||||
with None as its argument.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def set_protocol(self, protocol):
|
|
||||||
"""Set a new protocol."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_protocol(self):
|
|
||||||
"""Return the current protocol."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class ReadTransport(BaseTransport):
|
|
||||||
"""Interface for read-only transports."""
|
|
||||||
|
|
||||||
def pause_reading(self):
|
|
||||||
"""Pause the receiving end.
|
|
||||||
|
|
||||||
No data will be passed to the protocol's data_received()
|
|
||||||
method until resume_reading() is called.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def resume_reading(self):
|
|
||||||
"""Resume the receiving end.
|
|
||||||
|
|
||||||
Data received will once again be passed to the protocol's
|
|
||||||
data_received() method.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class WriteTransport(BaseTransport):
|
|
||||||
"""Interface for write-only transports."""
|
|
||||||
|
|
||||||
def set_write_buffer_limits(self, high=None, low=None):
|
|
||||||
"""Set the high- and low-water limits for write flow control.
|
|
||||||
|
|
||||||
These two values control when to call the protocol's
|
|
||||||
pause_writing() and resume_writing() methods. If specified,
|
|
||||||
the low-water limit must be less than or equal to the
|
|
||||||
high-water limit. Neither value can be negative.
|
|
||||||
|
|
||||||
The defaults are implementation-specific. If only the
|
|
||||||
high-water limit is given, the low-water limit defaults to an
|
|
||||||
implementation-specific value less than or equal to the
|
|
||||||
high-water limit. Setting high to zero forces low to zero as
|
|
||||||
well, and causes pause_writing() to be called whenever the
|
|
||||||
buffer becomes non-empty. Setting low to zero causes
|
|
||||||
resume_writing() to be called only once the buffer is empty.
|
|
||||||
Use of zero for either limit is generally sub-optimal as it
|
|
||||||
reduces opportunities for doing I/O and computation
|
|
||||||
concurrently.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_write_buffer_size(self):
|
|
||||||
"""Return the current size of the write buffer."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
"""Write some data bytes to the transport.
|
|
||||||
|
|
||||||
This does not block; it buffers the data and arranges for it
|
|
||||||
to be sent out asynchronously.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def writelines(self, list_of_data):
|
|
||||||
"""Write a list (or any iterable) of data bytes to the transport.
|
|
||||||
|
|
||||||
The default implementation concatenates the arguments and
|
|
||||||
calls write() on the result.
|
|
||||||
"""
|
|
||||||
data = compat.flatten_list_bytes(list_of_data)
|
|
||||||
self.write(data)
|
|
||||||
|
|
||||||
def write_eof(self):
|
|
||||||
"""Close the write end after flushing buffered data.
|
|
||||||
|
|
||||||
(This is like typing ^D into a UNIX program reading from stdin.)
|
|
||||||
|
|
||||||
Data may still be received.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def can_write_eof(self):
|
|
||||||
"""Return True if this transport supports write_eof(), False if not."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
"""Close the transport immediately.
|
|
||||||
|
|
||||||
Buffered data will be lost. No more data will be received.
|
|
||||||
The protocol's connection_lost() method will (eventually) be
|
|
||||||
called with None as its argument.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class Transport(ReadTransport, WriteTransport):
|
|
||||||
"""Interface representing a bidirectional transport.
|
|
||||||
|
|
||||||
There may be several implementations, but typically, the user does
|
|
||||||
not implement new transports; rather, the platform provides some
|
|
||||||
useful transports that are implemented using the platform's best
|
|
||||||
practices.
|
|
||||||
|
|
||||||
The user never instantiates a transport directly; they call a
|
|
||||||
utility function, passing it a protocol factory and other
|
|
||||||
information necessary to create the transport and protocol. (E.g.
|
|
||||||
EventLoop.create_connection() or EventLoop.create_server().)
|
|
||||||
|
|
||||||
The utility function will asynchronously create a transport and a
|
|
||||||
protocol and hook them up by calling the protocol's
|
|
||||||
connection_made() method, passing it the transport.
|
|
||||||
|
|
||||||
The implementation here raises NotImplemented for every method
|
|
||||||
except writelines(), which calls write() in a loop.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class DatagramTransport(BaseTransport):
|
|
||||||
"""Interface for datagram (UDP) transports."""
|
|
||||||
|
|
||||||
def sendto(self, data, addr=None):
|
|
||||||
"""Send data to the transport.
|
|
||||||
|
|
||||||
This does not block; it buffers the data and arranges for it
|
|
||||||
to be sent out asynchronously.
|
|
||||||
addr is target socket address.
|
|
||||||
If addr is None use target address pointed on transport creation.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def abort(self):
|
|
||||||
"""Close the transport immediately.
|
|
||||||
|
|
||||||
Buffered data will be lost. No more data will be received.
|
|
||||||
The protocol's connection_lost() method will (eventually) be
|
|
||||||
called with None as its argument.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class SubprocessTransport(BaseTransport):
|
|
||||||
|
|
||||||
def get_pid(self):
|
|
||||||
"""Get subprocess id."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_returncode(self):
|
|
||||||
"""Get subprocess returncode.
|
|
||||||
|
|
||||||
See also
|
|
||||||
http://docs.python.org/3/library/subprocess#subprocess.Popen.returncode
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_pipe_transport(self, fd):
|
|
||||||
"""Get transport for pipe with number fd."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def send_signal(self, signal):
|
|
||||||
"""Send signal to subprocess.
|
|
||||||
|
|
||||||
See also:
|
|
||||||
docs.python.org/3/library/subprocess#subprocess.Popen.send_signal
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def terminate(self):
|
|
||||||
"""Stop the subprocess.
|
|
||||||
|
|
||||||
Alias for close() method.
|
|
||||||
|
|
||||||
On Posix OSs the method sends SIGTERM to the subprocess.
|
|
||||||
On Windows the Win32 API function TerminateProcess()
|
|
||||||
is called to stop the subprocess.
|
|
||||||
|
|
||||||
See also:
|
|
||||||
http://docs.python.org/3/library/subprocess#subprocess.Popen.terminate
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def kill(self):
|
|
||||||
"""Kill the subprocess.
|
|
||||||
|
|
||||||
On Posix OSs the function sends SIGKILL to the subprocess.
|
|
||||||
On Windows kill() is an alias for terminate().
|
|
||||||
|
|
||||||
See also:
|
|
||||||
http://docs.python.org/3/library/subprocess#subprocess.Popen.kill
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class _FlowControlMixin(Transport):
|
|
||||||
"""All the logic for (write) flow control in a mix-in base class.
|
|
||||||
|
|
||||||
The subclass must implement get_write_buffer_size(). It must call
|
|
||||||
_maybe_pause_protocol() whenever the write buffer size increases,
|
|
||||||
and _maybe_resume_protocol() whenever it decreases. It may also
|
|
||||||
override set_write_buffer_limits() (e.g. to specify different
|
|
||||||
defaults).
|
|
||||||
|
|
||||||
The subclass constructor must call super().__init__(extra). This
|
|
||||||
will call set_write_buffer_limits().
|
|
||||||
|
|
||||||
The user may call set_write_buffer_limits() and
|
|
||||||
get_write_buffer_size(), and their protocol's pause_writing() and
|
|
||||||
resume_writing() may be called.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, extra=None, loop=None):
|
|
||||||
super().__init__(extra)
|
|
||||||
assert loop is not None
|
|
||||||
self._loop = loop
|
|
||||||
self._protocol_paused = False
|
|
||||||
self._set_write_buffer_limits()
|
|
||||||
|
|
||||||
def _maybe_pause_protocol(self):
|
|
||||||
size = self.get_write_buffer_size()
|
|
||||||
if size <= self._high_water:
|
|
||||||
return
|
|
||||||
if not self._protocol_paused:
|
|
||||||
self._protocol_paused = True
|
|
||||||
try:
|
|
||||||
self._protocol.pause_writing()
|
|
||||||
except Exception as exc:
|
|
||||||
self._loop.call_exception_handler({
|
|
||||||
'message': 'protocol.pause_writing() failed',
|
|
||||||
'exception': exc,
|
|
||||||
'transport': self,
|
|
||||||
'protocol': self._protocol,
|
|
||||||
})
|
|
||||||
|
|
||||||
def _maybe_resume_protocol(self):
|
|
||||||
if (self._protocol_paused and
|
|
||||||
self.get_write_buffer_size() <= self._low_water):
|
|
||||||
self._protocol_paused = False
|
|
||||||
try:
|
|
||||||
self._protocol.resume_writing()
|
|
||||||
except Exception as exc:
|
|
||||||
self._loop.call_exception_handler({
|
|
||||||
'message': 'protocol.resume_writing() failed',
|
|
||||||
'exception': exc,
|
|
||||||
'transport': self,
|
|
||||||
'protocol': self._protocol,
|
|
||||||
})
|
|
||||||
|
|
||||||
def get_write_buffer_limits(self):
|
|
||||||
return (self._low_water, self._high_water)
|
|
||||||
|
|
||||||
def _set_write_buffer_limits(self, high=None, low=None):
|
|
||||||
if high is None:
|
|
||||||
if low is None:
|
|
||||||
high = 64*1024
|
|
||||||
else:
|
|
||||||
high = 4*low
|
|
||||||
if low is None:
|
|
||||||
low = high // 4
|
|
||||||
if not high >= low >= 0:
|
|
||||||
raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
|
|
||||||
(high, low))
|
|
||||||
self._high_water = high
|
|
||||||
self._low_water = low
|
|
||||||
|
|
||||||
def set_write_buffer_limits(self, high=None, low=None):
|
|
||||||
self._set_write_buffer_limits(high=high, low=low)
|
|
||||||
self._maybe_pause_protocol()
|
|
||||||
|
|
||||||
def get_write_buffer_size(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,780 +0,0 @@
|
|||||||
"""Selector and proactor event loops for Windows."""
|
|
||||||
|
|
||||||
import _winapi
|
|
||||||
import errno
|
|
||||||
import math
|
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from . import events
|
|
||||||
from . import base_subprocess
|
|
||||||
from . import futures
|
|
||||||
from . import proactor_events
|
|
||||||
from . import selector_events
|
|
||||||
from . import tasks
|
|
||||||
from . import windows_utils
|
|
||||||
# XXX RustPython TODO: _overlapped
|
|
||||||
# from . import _overlapped
|
|
||||||
from .coroutines import coroutine
|
|
||||||
from .log import logger
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['SelectorEventLoop', 'ProactorEventLoop', 'IocpProactor',
|
|
||||||
'DefaultEventLoopPolicy',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
NULL = 0
|
|
||||||
INFINITE = 0xffffffff
|
|
||||||
ERROR_CONNECTION_REFUSED = 1225
|
|
||||||
ERROR_CONNECTION_ABORTED = 1236
|
|
||||||
|
|
||||||
# Initial delay in seconds for connect_pipe() before retrying to connect
|
|
||||||
CONNECT_PIPE_INIT_DELAY = 0.001
|
|
||||||
|
|
||||||
# Maximum delay in seconds for connect_pipe() before retrying to connect
|
|
||||||
CONNECT_PIPE_MAX_DELAY = 0.100
|
|
||||||
|
|
||||||
|
|
||||||
class _OverlappedFuture(futures.Future):
|
|
||||||
"""Subclass of Future which represents an overlapped operation.
|
|
||||||
|
|
||||||
Cancelling it will immediately cancel the overlapped operation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, ov, *, loop=None):
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
if self._source_traceback:
|
|
||||||
del self._source_traceback[-1]
|
|
||||||
self._ov = ov
|
|
||||||
|
|
||||||
def _repr_info(self):
|
|
||||||
info = super()._repr_info()
|
|
||||||
if self._ov is not None:
|
|
||||||
state = 'pending' if self._ov.pending else 'completed'
|
|
||||||
info.insert(1, 'overlapped=<%s, %#x>' % (state, self._ov.address))
|
|
||||||
return info
|
|
||||||
|
|
||||||
def _cancel_overlapped(self):
|
|
||||||
if self._ov is None:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self._ov.cancel()
|
|
||||||
except OSError as exc:
|
|
||||||
context = {
|
|
||||||
'message': 'Cancelling an overlapped future failed',
|
|
||||||
'exception': exc,
|
|
||||||
'future': self,
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
self._ov = None
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
self._cancel_overlapped()
|
|
||||||
return super().cancel()
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
super().set_exception(exception)
|
|
||||||
self._cancel_overlapped()
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
super().set_result(result)
|
|
||||||
self._ov = None
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseWaitHandleFuture(futures.Future):
|
|
||||||
"""Subclass of Future which represents a wait handle."""
|
|
||||||
|
|
||||||
def __init__(self, ov, handle, wait_handle, *, loop=None):
|
|
||||||
super().__init__(loop=loop)
|
|
||||||
if self._source_traceback:
|
|
||||||
del self._source_traceback[-1]
|
|
||||||
# Keep a reference to the Overlapped object to keep it alive until the
|
|
||||||
# wait is unregistered
|
|
||||||
self._ov = ov
|
|
||||||
self._handle = handle
|
|
||||||
self._wait_handle = wait_handle
|
|
||||||
|
|
||||||
# Should we call UnregisterWaitEx() if the wait completes
|
|
||||||
# or is cancelled?
|
|
||||||
self._registered = True
|
|
||||||
|
|
||||||
def _poll(self):
|
|
||||||
# non-blocking wait: use a timeout of 0 millisecond
|
|
||||||
return (_winapi.WaitForSingleObject(self._handle, 0) ==
|
|
||||||
_winapi.WAIT_OBJECT_0)
|
|
||||||
|
|
||||||
def _repr_info(self):
|
|
||||||
info = super()._repr_info()
|
|
||||||
info.append('handle=%#x' % self._handle)
|
|
||||||
if self._handle is not None:
|
|
||||||
state = 'signaled' if self._poll() else 'waiting'
|
|
||||||
info.append(state)
|
|
||||||
if self._wait_handle is not None:
|
|
||||||
info.append('wait_handle=%#x' % self._wait_handle)
|
|
||||||
return info
|
|
||||||
|
|
||||||
def _unregister_wait_cb(self, fut):
|
|
||||||
# The wait was unregistered: it's not safe to destroy the Overlapped
|
|
||||||
# object
|
|
||||||
self._ov = None
|
|
||||||
|
|
||||||
def _unregister_wait(self):
|
|
||||||
if not self._registered:
|
|
||||||
return
|
|
||||||
self._registered = False
|
|
||||||
|
|
||||||
wait_handle = self._wait_handle
|
|
||||||
self._wait_handle = None
|
|
||||||
try:
|
|
||||||
_overlapped.UnregisterWait(wait_handle)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror != _overlapped.ERROR_IO_PENDING:
|
|
||||||
context = {
|
|
||||||
'message': 'Failed to unregister the wait handle',
|
|
||||||
'exception': exc,
|
|
||||||
'future': self,
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
return
|
|
||||||
# ERROR_IO_PENDING means that the unregister is pending
|
|
||||||
|
|
||||||
self._unregister_wait_cb(None)
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
self._unregister_wait()
|
|
||||||
return super().cancel()
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
self._unregister_wait()
|
|
||||||
super().set_exception(exception)
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
self._unregister_wait()
|
|
||||||
super().set_result(result)
|
|
||||||
|
|
||||||
|
|
||||||
class _WaitCancelFuture(_BaseWaitHandleFuture):
|
|
||||||
"""Subclass of Future which represents a wait for the cancellation of a
|
|
||||||
_WaitHandleFuture using an event.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, ov, event, wait_handle, *, loop=None):
|
|
||||||
super().__init__(ov, event, wait_handle, loop=loop)
|
|
||||||
|
|
||||||
self._done_callback = None
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
raise RuntimeError("_WaitCancelFuture must not be cancelled")
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
super().set_result(result)
|
|
||||||
if self._done_callback is not None:
|
|
||||||
self._done_callback(self)
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
super().set_exception(exception)
|
|
||||||
if self._done_callback is not None:
|
|
||||||
self._done_callback(self)
|
|
||||||
|
|
||||||
|
|
||||||
class _WaitHandleFuture(_BaseWaitHandleFuture):
|
|
||||||
def __init__(self, ov, handle, wait_handle, proactor, *, loop=None):
|
|
||||||
super().__init__(ov, handle, wait_handle, loop=loop)
|
|
||||||
self._proactor = proactor
|
|
||||||
self._unregister_proactor = True
|
|
||||||
self._event = _overlapped.CreateEvent(None, True, False, None)
|
|
||||||
self._event_fut = None
|
|
||||||
|
|
||||||
def _unregister_wait_cb(self, fut):
|
|
||||||
if self._event is not None:
|
|
||||||
_winapi.CloseHandle(self._event)
|
|
||||||
self._event = None
|
|
||||||
self._event_fut = None
|
|
||||||
|
|
||||||
# If the wait was cancelled, the wait may never be signalled, so
|
|
||||||
# it's required to unregister it. Otherwise, IocpProactor.close() will
|
|
||||||
# wait forever for an event which will never come.
|
|
||||||
#
|
|
||||||
# If the IocpProactor already received the event, it's safe to call
|
|
||||||
# _unregister() because we kept a reference to the Overlapped object
|
|
||||||
# which is used as a unique key.
|
|
||||||
self._proactor._unregister(self._ov)
|
|
||||||
self._proactor = None
|
|
||||||
|
|
||||||
super()._unregister_wait_cb(fut)
|
|
||||||
|
|
||||||
def _unregister_wait(self):
|
|
||||||
if not self._registered:
|
|
||||||
return
|
|
||||||
self._registered = False
|
|
||||||
|
|
||||||
wait_handle = self._wait_handle
|
|
||||||
self._wait_handle = None
|
|
||||||
try:
|
|
||||||
_overlapped.UnregisterWaitEx(wait_handle, self._event)
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror != _overlapped.ERROR_IO_PENDING:
|
|
||||||
context = {
|
|
||||||
'message': 'Failed to unregister the wait handle',
|
|
||||||
'exception': exc,
|
|
||||||
'future': self,
|
|
||||||
}
|
|
||||||
if self._source_traceback:
|
|
||||||
context['source_traceback'] = self._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
return
|
|
||||||
# ERROR_IO_PENDING is not an error, the wait was unregistered
|
|
||||||
|
|
||||||
self._event_fut = self._proactor._wait_cancel(self._event,
|
|
||||||
self._unregister_wait_cb)
|
|
||||||
|
|
||||||
|
|
||||||
class PipeServer(object):
|
|
||||||
"""Class representing a pipe server.
|
|
||||||
|
|
||||||
This is much like a bound, listening socket.
|
|
||||||
"""
|
|
||||||
def __init__(self, address):
|
|
||||||
self._address = address
|
|
||||||
self._free_instances = weakref.WeakSet()
|
|
||||||
# initialize the pipe attribute before calling _server_pipe_handle()
|
|
||||||
# because this function can raise an exception and the destructor calls
|
|
||||||
# the close() method
|
|
||||||
self._pipe = None
|
|
||||||
self._accept_pipe_future = None
|
|
||||||
self._pipe = self._server_pipe_handle(True)
|
|
||||||
|
|
||||||
def _get_unconnected_pipe(self):
|
|
||||||
# Create new instance and return previous one. This ensures
|
|
||||||
# that (until the server is closed) there is always at least
|
|
||||||
# one pipe handle for address. Therefore if a client attempt
|
|
||||||
# to connect it will not fail with FileNotFoundError.
|
|
||||||
tmp, self._pipe = self._pipe, self._server_pipe_handle(False)
|
|
||||||
return tmp
|
|
||||||
|
|
||||||
def _server_pipe_handle(self, first):
|
|
||||||
# Return a wrapper for a new pipe handle.
|
|
||||||
if self.closed():
|
|
||||||
return None
|
|
||||||
flags = _winapi.PIPE_ACCESS_DUPLEX | _winapi.FILE_FLAG_OVERLAPPED
|
|
||||||
if first:
|
|
||||||
flags |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
|
|
||||||
h = _winapi.CreateNamedPipe(
|
|
||||||
self._address, flags,
|
|
||||||
_winapi.PIPE_TYPE_MESSAGE | _winapi.PIPE_READMODE_MESSAGE |
|
|
||||||
_winapi.PIPE_WAIT,
|
|
||||||
_winapi.PIPE_UNLIMITED_INSTANCES,
|
|
||||||
windows_utils.BUFSIZE, windows_utils.BUFSIZE,
|
|
||||||
_winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
|
|
||||||
pipe = windows_utils.PipeHandle(h)
|
|
||||||
self._free_instances.add(pipe)
|
|
||||||
return pipe
|
|
||||||
|
|
||||||
def closed(self):
|
|
||||||
return (self._address is None)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if self._accept_pipe_future is not None:
|
|
||||||
self._accept_pipe_future.cancel()
|
|
||||||
self._accept_pipe_future = None
|
|
||||||
# Close all instances which have not been connected to by a client.
|
|
||||||
if self._address is not None:
|
|
||||||
for pipe in self._free_instances:
|
|
||||||
pipe.close()
|
|
||||||
self._pipe = None
|
|
||||||
self._address = None
|
|
||||||
self._free_instances.clear()
|
|
||||||
|
|
||||||
__del__ = close
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|
||||||
"""Windows version of selector event loop."""
|
|
||||||
|
|
||||||
def _socketpair(self):
|
|
||||||
return windows_utils.socketpair()
|
|
||||||
|
|
||||||
|
|
||||||
class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
|
||||||
"""Windows version of proactor event loop using IOCP."""
|
|
||||||
|
|
||||||
def __init__(self, proactor=None):
|
|
||||||
if proactor is None:
|
|
||||||
proactor = IocpProactor()
|
|
||||||
super().__init__(proactor)
|
|
||||||
|
|
||||||
def _socketpair(self):
|
|
||||||
return windows_utils.socketpair()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def create_pipe_connection(self, protocol_factory, address):
|
|
||||||
f = self._proactor.connect_pipe(address)
|
|
||||||
pipe = yield from f
|
|
||||||
protocol = protocol_factory()
|
|
||||||
trans = self._make_duplex_pipe_transport(pipe, protocol,
|
|
||||||
extra={'addr': address})
|
|
||||||
return trans, protocol
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def start_serving_pipe(self, protocol_factory, address):
|
|
||||||
server = PipeServer(address)
|
|
||||||
|
|
||||||
def loop_accept_pipe(f=None):
|
|
||||||
pipe = None
|
|
||||||
try:
|
|
||||||
if f:
|
|
||||||
pipe = f.result()
|
|
||||||
server._free_instances.discard(pipe)
|
|
||||||
|
|
||||||
if server.closed():
|
|
||||||
# A client connected before the server was closed:
|
|
||||||
# drop the client (close the pipe) and exit
|
|
||||||
pipe.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
protocol = protocol_factory()
|
|
||||||
self._make_duplex_pipe_transport(
|
|
||||||
pipe, protocol, extra={'addr': address})
|
|
||||||
|
|
||||||
pipe = server._get_unconnected_pipe()
|
|
||||||
if pipe is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
f = self._proactor.accept_pipe(pipe)
|
|
||||||
except OSError as exc:
|
|
||||||
if pipe and pipe.fileno() != -1:
|
|
||||||
self.call_exception_handler({
|
|
||||||
'message': 'Pipe accept failed',
|
|
||||||
'exception': exc,
|
|
||||||
'pipe': pipe,
|
|
||||||
})
|
|
||||||
pipe.close()
|
|
||||||
elif self._debug:
|
|
||||||
logger.warning("Accept pipe failed on pipe %r",
|
|
||||||
pipe, exc_info=True)
|
|
||||||
except futures.CancelledError:
|
|
||||||
if pipe:
|
|
||||||
pipe.close()
|
|
||||||
else:
|
|
||||||
server._accept_pipe_future = f
|
|
||||||
f.add_done_callback(loop_accept_pipe)
|
|
||||||
|
|
||||||
self.call_soon(loop_accept_pipe)
|
|
||||||
return [server]
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def _make_subprocess_transport(self, protocol, args, shell,
|
|
||||||
stdin, stdout, stderr, bufsize,
|
|
||||||
extra=None, **kwargs):
|
|
||||||
waiter = self.create_future()
|
|
||||||
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
|
|
||||||
stdin, stdout, stderr, bufsize,
|
|
||||||
waiter=waiter, extra=extra,
|
|
||||||
**kwargs)
|
|
||||||
try:
|
|
||||||
yield from waiter
|
|
||||||
except Exception as exc:
|
|
||||||
# Workaround CPython bug #23353: using yield/yield-from in an
|
|
||||||
# except block of a generator doesn't clear properly sys.exc_info()
|
|
||||||
err = exc
|
|
||||||
else:
|
|
||||||
err = None
|
|
||||||
|
|
||||||
if err is not None:
|
|
||||||
transp.close()
|
|
||||||
yield from transp._wait()
|
|
||||||
raise err
|
|
||||||
|
|
||||||
return transp
|
|
||||||
|
|
||||||
|
|
||||||
class IocpProactor:
|
|
||||||
"""Proactor implementation using IOCP."""
|
|
||||||
|
|
||||||
def __init__(self, concurrency=0xffffffff):
|
|
||||||
self._loop = None
|
|
||||||
self._results = []
|
|
||||||
self._iocp = _overlapped.CreateIoCompletionPort(
|
|
||||||
_overlapped.INVALID_HANDLE_VALUE, NULL, 0, concurrency)
|
|
||||||
self._cache = {}
|
|
||||||
self._registered = weakref.WeakSet()
|
|
||||||
self._unregistered = []
|
|
||||||
self._stopped_serving = weakref.WeakSet()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return ('<%s overlapped#=%s result#=%s>'
|
|
||||||
% (self.__class__.__name__, len(self._cache),
|
|
||||||
len(self._results)))
|
|
||||||
|
|
||||||
def set_loop(self, loop):
|
|
||||||
self._loop = loop
|
|
||||||
|
|
||||||
def select(self, timeout=None):
|
|
||||||
if not self._results:
|
|
||||||
self._poll(timeout)
|
|
||||||
tmp = self._results
|
|
||||||
self._results = []
|
|
||||||
return tmp
|
|
||||||
|
|
||||||
def _result(self, value):
|
|
||||||
fut = self._loop.create_future()
|
|
||||||
fut.set_result(value)
|
|
||||||
return fut
|
|
||||||
|
|
||||||
def recv(self, conn, nbytes, flags=0):
|
|
||||||
self._register_with_iocp(conn)
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
try:
|
|
||||||
if isinstance(conn, socket.socket):
|
|
||||||
ov.WSARecv(conn.fileno(), nbytes, flags)
|
|
||||||
else:
|
|
||||||
ov.ReadFile(conn.fileno(), nbytes)
|
|
||||||
except BrokenPipeError:
|
|
||||||
return self._result(b'')
|
|
||||||
|
|
||||||
def finish_recv(trans, key, ov):
|
|
||||||
try:
|
|
||||||
return ov.getresult()
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
|
|
||||||
raise ConnectionResetError(*exc.args)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return self._register(ov, conn, finish_recv)
|
|
||||||
|
|
||||||
def send(self, conn, buf, flags=0):
|
|
||||||
self._register_with_iocp(conn)
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
if isinstance(conn, socket.socket):
|
|
||||||
ov.WSASend(conn.fileno(), buf, flags)
|
|
||||||
else:
|
|
||||||
ov.WriteFile(conn.fileno(), buf)
|
|
||||||
|
|
||||||
def finish_send(trans, key, ov):
|
|
||||||
try:
|
|
||||||
return ov.getresult()
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror == _overlapped.ERROR_NETNAME_DELETED:
|
|
||||||
raise ConnectionResetError(*exc.args)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
return self._register(ov, conn, finish_send)
|
|
||||||
|
|
||||||
def accept(self, listener):
|
|
||||||
self._register_with_iocp(listener)
|
|
||||||
conn = self._get_accept_socket(listener.family)
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
ov.AcceptEx(listener.fileno(), conn.fileno())
|
|
||||||
|
|
||||||
def finish_accept(trans, key, ov):
|
|
||||||
ov.getresult()
|
|
||||||
# Use SO_UPDATE_ACCEPT_CONTEXT so getsockname() etc work.
|
|
||||||
buf = struct.pack('@P', listener.fileno())
|
|
||||||
conn.setsockopt(socket.SOL_SOCKET,
|
|
||||||
_overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf)
|
|
||||||
conn.settimeout(listener.gettimeout())
|
|
||||||
return conn, conn.getpeername()
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def accept_coro(future, conn):
|
|
||||||
# Coroutine closing the accept socket if the future is cancelled
|
|
||||||
try:
|
|
||||||
yield from future
|
|
||||||
except futures.CancelledError:
|
|
||||||
conn.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
future = self._register(ov, listener, finish_accept)
|
|
||||||
coro = accept_coro(future, conn)
|
|
||||||
tasks.ensure_future(coro, loop=self._loop)
|
|
||||||
return future
|
|
||||||
|
|
||||||
def connect(self, conn, address):
|
|
||||||
self._register_with_iocp(conn)
|
|
||||||
# The socket needs to be locally bound before we call ConnectEx().
|
|
||||||
try:
|
|
||||||
_overlapped.BindLocal(conn.fileno(), conn.family)
|
|
||||||
except OSError as e:
|
|
||||||
if e.winerror != errno.WSAEINVAL:
|
|
||||||
raise
|
|
||||||
# Probably already locally bound; check using getsockname().
|
|
||||||
if conn.getsockname()[1] == 0:
|
|
||||||
raise
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
ov.ConnectEx(conn.fileno(), address)
|
|
||||||
|
|
||||||
def finish_connect(trans, key, ov):
|
|
||||||
ov.getresult()
|
|
||||||
# Use SO_UPDATE_CONNECT_CONTEXT so getsockname() etc work.
|
|
||||||
conn.setsockopt(socket.SOL_SOCKET,
|
|
||||||
_overlapped.SO_UPDATE_CONNECT_CONTEXT, 0)
|
|
||||||
return conn
|
|
||||||
|
|
||||||
return self._register(ov, conn, finish_connect)
|
|
||||||
|
|
||||||
def accept_pipe(self, pipe):
|
|
||||||
self._register_with_iocp(pipe)
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
connected = ov.ConnectNamedPipe(pipe.fileno())
|
|
||||||
|
|
||||||
if connected:
|
|
||||||
# ConnectNamePipe() failed with ERROR_PIPE_CONNECTED which means
|
|
||||||
# that the pipe is connected. There is no need to wait for the
|
|
||||||
# completion of the connection.
|
|
||||||
return self._result(pipe)
|
|
||||||
|
|
||||||
def finish_accept_pipe(trans, key, ov):
|
|
||||||
ov.getresult()
|
|
||||||
return pipe
|
|
||||||
|
|
||||||
return self._register(ov, pipe, finish_accept_pipe)
|
|
||||||
|
|
||||||
@coroutine
|
|
||||||
def connect_pipe(self, address):
|
|
||||||
delay = CONNECT_PIPE_INIT_DELAY
|
|
||||||
while True:
|
|
||||||
# Unfortunately there is no way to do an overlapped connect to a pipe.
|
|
||||||
# Call CreateFile() in a loop until it doesn't fail with
|
|
||||||
# ERROR_PIPE_BUSY
|
|
||||||
try:
|
|
||||||
handle = _overlapped.ConnectPipe(address)
|
|
||||||
break
|
|
||||||
except OSError as exc:
|
|
||||||
if exc.winerror != _overlapped.ERROR_PIPE_BUSY:
|
|
||||||
raise
|
|
||||||
|
|
||||||
# ConnectPipe() failed with ERROR_PIPE_BUSY: retry later
|
|
||||||
delay = min(delay * 2, CONNECT_PIPE_MAX_DELAY)
|
|
||||||
yield from tasks.sleep(delay, loop=self._loop)
|
|
||||||
|
|
||||||
return windows_utils.PipeHandle(handle)
|
|
||||||
|
|
||||||
def wait_for_handle(self, handle, timeout=None):
|
|
||||||
"""Wait for a handle.
|
|
||||||
|
|
||||||
Return a Future object. The result of the future is True if the wait
|
|
||||||
completed, or False if the wait did not complete (on timeout).
|
|
||||||
"""
|
|
||||||
return self._wait_for_handle(handle, timeout, False)
|
|
||||||
|
|
||||||
def _wait_cancel(self, event, done_callback):
|
|
||||||
fut = self._wait_for_handle(event, None, True)
|
|
||||||
# add_done_callback() cannot be used because the wait may only complete
|
|
||||||
# in IocpProactor.close(), while the event loop is not running.
|
|
||||||
fut._done_callback = done_callback
|
|
||||||
return fut
|
|
||||||
|
|
||||||
def _wait_for_handle(self, handle, timeout, _is_cancel):
|
|
||||||
if timeout is None:
|
|
||||||
ms = _winapi.INFINITE
|
|
||||||
else:
|
|
||||||
# RegisterWaitForSingleObject() has a resolution of 1 millisecond,
|
|
||||||
# round away from zero to wait *at least* timeout seconds.
|
|
||||||
ms = math.ceil(timeout * 1e3)
|
|
||||||
|
|
||||||
# We only create ov so we can use ov.address as a key for the cache.
|
|
||||||
ov = _overlapped.Overlapped(NULL)
|
|
||||||
wait_handle = _overlapped.RegisterWaitWithQueue(
|
|
||||||
handle, self._iocp, ov.address, ms)
|
|
||||||
if _is_cancel:
|
|
||||||
f = _WaitCancelFuture(ov, handle, wait_handle, loop=self._loop)
|
|
||||||
else:
|
|
||||||
f = _WaitHandleFuture(ov, handle, wait_handle, self,
|
|
||||||
loop=self._loop)
|
|
||||||
if f._source_traceback:
|
|
||||||
del f._source_traceback[-1]
|
|
||||||
|
|
||||||
def finish_wait_for_handle(trans, key, ov):
|
|
||||||
# Note that this second wait means that we should only use
|
|
||||||
# this with handles types where a successful wait has no
|
|
||||||
# effect. So events or processes are all right, but locks
|
|
||||||
# or semaphores are not. Also note if the handle is
|
|
||||||
# signalled and then quickly reset, then we may return
|
|
||||||
# False even though we have not timed out.
|
|
||||||
return f._poll()
|
|
||||||
|
|
||||||
self._cache[ov.address] = (f, ov, 0, finish_wait_for_handle)
|
|
||||||
return f
|
|
||||||
|
|
||||||
def _register_with_iocp(self, obj):
|
|
||||||
# To get notifications of finished ops on this objects sent to the
|
|
||||||
# completion port, were must register the handle.
|
|
||||||
if obj not in self._registered:
|
|
||||||
self._registered.add(obj)
|
|
||||||
_overlapped.CreateIoCompletionPort(obj.fileno(), self._iocp, 0, 0)
|
|
||||||
# XXX We could also use SetFileCompletionNotificationModes()
|
|
||||||
# to avoid sending notifications to completion port of ops
|
|
||||||
# that succeed immediately.
|
|
||||||
|
|
||||||
def _register(self, ov, obj, callback):
|
|
||||||
# Return a future which will be set with the result of the
|
|
||||||
# operation when it completes. The future's value is actually
|
|
||||||
# the value returned by callback().
|
|
||||||
f = _OverlappedFuture(ov, loop=self._loop)
|
|
||||||
if f._source_traceback:
|
|
||||||
del f._source_traceback[-1]
|
|
||||||
if not ov.pending:
|
|
||||||
# The operation has completed, so no need to postpone the
|
|
||||||
# work. We cannot take this short cut if we need the
|
|
||||||
# NumberOfBytes, CompletionKey values returned by
|
|
||||||
# PostQueuedCompletionStatus().
|
|
||||||
try:
|
|
||||||
value = callback(None, None, ov)
|
|
||||||
except OSError as e:
|
|
||||||
f.set_exception(e)
|
|
||||||
else:
|
|
||||||
f.set_result(value)
|
|
||||||
# Even if GetOverlappedResult() was called, we have to wait for the
|
|
||||||
# notification of the completion in GetQueuedCompletionStatus().
|
|
||||||
# Register the overlapped operation to keep a reference to the
|
|
||||||
# OVERLAPPED object, otherwise the memory is freed and Windows may
|
|
||||||
# read uninitialized memory.
|
|
||||||
|
|
||||||
# Register the overlapped operation for later. Note that
|
|
||||||
# we only store obj to prevent it from being garbage
|
|
||||||
# collected too early.
|
|
||||||
self._cache[ov.address] = (f, ov, obj, callback)
|
|
||||||
return f
|
|
||||||
|
|
||||||
def _unregister(self, ov):
|
|
||||||
"""Unregister an overlapped object.
|
|
||||||
|
|
||||||
Call this method when its future has been cancelled. The event can
|
|
||||||
already be signalled (pending in the proactor event queue). It is also
|
|
||||||
safe if the event is never signalled (because it was cancelled).
|
|
||||||
"""
|
|
||||||
self._unregistered.append(ov)
|
|
||||||
|
|
||||||
def _get_accept_socket(self, family):
|
|
||||||
s = socket.socket(family)
|
|
||||||
s.settimeout(0)
|
|
||||||
return s
|
|
||||||
|
|
||||||
def _poll(self, timeout=None):
|
|
||||||
if timeout is None:
|
|
||||||
ms = INFINITE
|
|
||||||
elif timeout < 0:
|
|
||||||
raise ValueError("negative timeout")
|
|
||||||
else:
|
|
||||||
# GetQueuedCompletionStatus() has a resolution of 1 millisecond,
|
|
||||||
# round away from zero to wait *at least* timeout seconds.
|
|
||||||
ms = math.ceil(timeout * 1e3)
|
|
||||||
if ms >= INFINITE:
|
|
||||||
raise ValueError("timeout too big")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
status = _overlapped.GetQueuedCompletionStatus(self._iocp, ms)
|
|
||||||
if status is None:
|
|
||||||
break
|
|
||||||
ms = 0
|
|
||||||
|
|
||||||
err, transferred, key, address = status
|
|
||||||
try:
|
|
||||||
f, ov, obj, callback = self._cache.pop(address)
|
|
||||||
except KeyError:
|
|
||||||
if self._loop.get_debug():
|
|
||||||
self._loop.call_exception_handler({
|
|
||||||
'message': ('GetQueuedCompletionStatus() returned an '
|
|
||||||
'unexpected event'),
|
|
||||||
'status': ('err=%s transferred=%s key=%#x address=%#x'
|
|
||||||
% (err, transferred, key, address)),
|
|
||||||
})
|
|
||||||
|
|
||||||
# key is either zero, or it is used to return a pipe
|
|
||||||
# handle which should be closed to avoid a leak.
|
|
||||||
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
|
|
||||||
_winapi.CloseHandle(key)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if obj in self._stopped_serving:
|
|
||||||
f.cancel()
|
|
||||||
# Don't call the callback if _register() already read the result or
|
|
||||||
# if the overlapped has been cancelled
|
|
||||||
elif not f.done():
|
|
||||||
try:
|
|
||||||
value = callback(transferred, key, ov)
|
|
||||||
except OSError as e:
|
|
||||||
f.set_exception(e)
|
|
||||||
self._results.append(f)
|
|
||||||
else:
|
|
||||||
f.set_result(value)
|
|
||||||
self._results.append(f)
|
|
||||||
|
|
||||||
# Remove unregisted futures
|
|
||||||
for ov in self._unregistered:
|
|
||||||
self._cache.pop(ov.address, None)
|
|
||||||
self._unregistered.clear()
|
|
||||||
|
|
||||||
def _stop_serving(self, obj):
|
|
||||||
# obj is a socket or pipe handle. It will be closed in
|
|
||||||
# BaseProactorEventLoop._stop_serving() which will make any
|
|
||||||
# pending operations fail quickly.
|
|
||||||
self._stopped_serving.add(obj)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
# Cancel remaining registered operations.
|
|
||||||
for address, (fut, ov, obj, callback) in list(self._cache.items()):
|
|
||||||
if fut.cancelled():
|
|
||||||
# Nothing to do with cancelled futures
|
|
||||||
pass
|
|
||||||
elif isinstance(fut, _WaitCancelFuture):
|
|
||||||
# _WaitCancelFuture must not be cancelled
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
fut.cancel()
|
|
||||||
except OSError as exc:
|
|
||||||
if self._loop is not None:
|
|
||||||
context = {
|
|
||||||
'message': 'Cancelling a future failed',
|
|
||||||
'exception': exc,
|
|
||||||
'future': fut,
|
|
||||||
}
|
|
||||||
if fut._source_traceback:
|
|
||||||
context['source_traceback'] = fut._source_traceback
|
|
||||||
self._loop.call_exception_handler(context)
|
|
||||||
|
|
||||||
while self._cache:
|
|
||||||
if not self._poll(1):
|
|
||||||
logger.debug('taking long time to close proactor')
|
|
||||||
|
|
||||||
self._results = []
|
|
||||||
if self._iocp is not None:
|
|
||||||
_winapi.CloseHandle(self._iocp)
|
|
||||||
self._iocp = None
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsSubprocessTransport(base_subprocess.BaseSubprocessTransport):
|
|
||||||
|
|
||||||
def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
|
|
||||||
self._proc = windows_utils.Popen(
|
|
||||||
args, shell=shell, stdin=stdin, stdout=stdout, stderr=stderr,
|
|
||||||
bufsize=bufsize, **kwargs)
|
|
||||||
|
|
||||||
def callback(f):
|
|
||||||
returncode = self._proc.poll()
|
|
||||||
self._process_exited(returncode)
|
|
||||||
|
|
||||||
f = self._loop._proactor.wait_for_handle(int(self._proc._handle))
|
|
||||||
f.add_done_callback(callback)
|
|
||||||
|
|
||||||
|
|
||||||
SelectorEventLoop = _WindowsSelectorEventLoop
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
|
|
||||||
_loop_factory = SelectorEventLoop
|
|
||||||
|
|
||||||
|
|
||||||
DefaultEventLoopPolicy = _WindowsDefaultEventLoopPolicy
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
"""
|
|
||||||
Various Windows specific bits and pieces
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.platform != 'win32': # pragma: no cover
|
|
||||||
raise ImportError('win32 only')
|
|
||||||
|
|
||||||
import _winapi
|
|
||||||
import itertools
|
|
||||||
# XXX RustPython TODO: msvcrt
|
|
||||||
# import msvcrt
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['socketpair', 'pipe', 'Popen', 'PIPE', 'PipeHandle']
|
|
||||||
|
|
||||||
|
|
||||||
# Constants/globals
|
|
||||||
|
|
||||||
|
|
||||||
BUFSIZE = 8192
|
|
||||||
PIPE = subprocess.PIPE
|
|
||||||
STDOUT = subprocess.STDOUT
|
|
||||||
_mmap_counter = itertools.count()
|
|
||||||
|
|
||||||
|
|
||||||
if hasattr(socket, 'socketpair'):
|
|
||||||
# Since Python 3.5, socket.socketpair() is now also available on Windows
|
|
||||||
socketpair = socket.socketpair
|
|
||||||
else:
|
|
||||||
# Replacement for socket.socketpair()
|
|
||||||
def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
|
|
||||||
"""A socket pair usable as a self-pipe, for Windows.
|
|
||||||
|
|
||||||
Origin: https://gist.github.com/4325783, by Geert Jansen.
|
|
||||||
Public domain.
|
|
||||||
"""
|
|
||||||
if family == socket.AF_INET:
|
|
||||||
host = '127.0.0.1'
|
|
||||||
elif family == socket.AF_INET6:
|
|
||||||
host = '::1'
|
|
||||||
else:
|
|
||||||
raise ValueError("Only AF_INET and AF_INET6 socket address "
|
|
||||||
"families are supported")
|
|
||||||
if type != socket.SOCK_STREAM:
|
|
||||||
raise ValueError("Only SOCK_STREAM socket type is supported")
|
|
||||||
if proto != 0:
|
|
||||||
raise ValueError("Only protocol zero is supported")
|
|
||||||
|
|
||||||
# We create a connected TCP socket. Note the trick with setblocking(0)
|
|
||||||
# that prevents us from having to create a thread.
|
|
||||||
lsock = socket.socket(family, type, proto)
|
|
||||||
try:
|
|
||||||
lsock.bind((host, 0))
|
|
||||||
lsock.listen(1)
|
|
||||||
# On IPv6, ignore flow_info and scope_id
|
|
||||||
addr, port = lsock.getsockname()[:2]
|
|
||||||
csock = socket.socket(family, type, proto)
|
|
||||||
try:
|
|
||||||
csock.setblocking(False)
|
|
||||||
try:
|
|
||||||
csock.connect((addr, port))
|
|
||||||
except (BlockingIOError, InterruptedError):
|
|
||||||
pass
|
|
||||||
csock.setblocking(True)
|
|
||||||
ssock, _ = lsock.accept()
|
|
||||||
except:
|
|
||||||
csock.close()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
lsock.close()
|
|
||||||
return (ssock, csock)
|
|
||||||
|
|
||||||
|
|
||||||
# Replacement for os.pipe() using handles instead of fds
|
|
||||||
|
|
||||||
|
|
||||||
def pipe(*, duplex=False, overlapped=(True, True), bufsize=BUFSIZE):
|
|
||||||
"""Like os.pipe() but with overlapped support and using handles not fds."""
|
|
||||||
address = tempfile.mktemp(prefix=r'\\.\pipe\python-pipe-%d-%d-' %
|
|
||||||
(os.getpid(), next(_mmap_counter)))
|
|
||||||
|
|
||||||
if duplex:
|
|
||||||
openmode = _winapi.PIPE_ACCESS_DUPLEX
|
|
||||||
access = _winapi.GENERIC_READ | _winapi.GENERIC_WRITE
|
|
||||||
obsize, ibsize = bufsize, bufsize
|
|
||||||
else:
|
|
||||||
openmode = _winapi.PIPE_ACCESS_INBOUND
|
|
||||||
access = _winapi.GENERIC_WRITE
|
|
||||||
obsize, ibsize = 0, bufsize
|
|
||||||
|
|
||||||
openmode |= _winapi.FILE_FLAG_FIRST_PIPE_INSTANCE
|
|
||||||
|
|
||||||
if overlapped[0]:
|
|
||||||
openmode |= _winapi.FILE_FLAG_OVERLAPPED
|
|
||||||
|
|
||||||
if overlapped[1]:
|
|
||||||
flags_and_attribs = _winapi.FILE_FLAG_OVERLAPPED
|
|
||||||
else:
|
|
||||||
flags_and_attribs = 0
|
|
||||||
|
|
||||||
h1 = h2 = None
|
|
||||||
try:
|
|
||||||
h1 = _winapi.CreateNamedPipe(
|
|
||||||
address, openmode, _winapi.PIPE_WAIT,
|
|
||||||
1, obsize, ibsize, _winapi.NMPWAIT_WAIT_FOREVER, _winapi.NULL)
|
|
||||||
|
|
||||||
h2 = _winapi.CreateFile(
|
|
||||||
address, access, 0, _winapi.NULL, _winapi.OPEN_EXISTING,
|
|
||||||
flags_and_attribs, _winapi.NULL)
|
|
||||||
|
|
||||||
ov = _winapi.ConnectNamedPipe(h1, overlapped=True)
|
|
||||||
ov.GetOverlappedResult(True)
|
|
||||||
return h1, h2
|
|
||||||
except:
|
|
||||||
if h1 is not None:
|
|
||||||
_winapi.CloseHandle(h1)
|
|
||||||
if h2 is not None:
|
|
||||||
_winapi.CloseHandle(h2)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
# Wrapper for a pipe handle
|
|
||||||
|
|
||||||
|
|
||||||
class PipeHandle:
|
|
||||||
"""Wrapper for an overlapped pipe handle which is vaguely file-object like.
|
|
||||||
|
|
||||||
The IOCP event loop can use these instead of socket objects.
|
|
||||||
"""
|
|
||||||
def __init__(self, handle):
|
|
||||||
self._handle = handle
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self._handle is not None:
|
|
||||||
handle = 'handle=%r' % self._handle
|
|
||||||
else:
|
|
||||||
handle = 'closed'
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, handle)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def handle(self):
|
|
||||||
return self._handle
|
|
||||||
|
|
||||||
def fileno(self):
|
|
||||||
if self._handle is None:
|
|
||||||
raise ValueError("I/O operatioon on closed pipe")
|
|
||||||
return self._handle
|
|
||||||
|
|
||||||
def close(self, *, CloseHandle=_winapi.CloseHandle):
|
|
||||||
if self._handle is not None:
|
|
||||||
CloseHandle(self._handle)
|
|
||||||
self._handle = None
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self._handle is not None:
|
|
||||||
warnings.warn("unclosed %r" % self, ResourceWarning,
|
|
||||||
source=self)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, t, v, tb):
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
# Replacement for subprocess.Popen using overlapped pipe handles
|
|
||||||
|
|
||||||
|
|
||||||
class Popen(subprocess.Popen):
|
|
||||||
"""Replacement for subprocess.Popen using overlapped pipe handles.
|
|
||||||
|
|
||||||
The stdin, stdout, stderr are None or instances of PipeHandle.
|
|
||||||
"""
|
|
||||||
def __init__(self, args, stdin=None, stdout=None, stderr=None, **kwds):
|
|
||||||
assert not kwds.get('universal_newlines')
|
|
||||||
assert kwds.get('bufsize', 0) == 0
|
|
||||||
stdin_rfd = stdout_wfd = stderr_wfd = None
|
|
||||||
stdin_wh = stdout_rh = stderr_rh = None
|
|
||||||
if stdin == PIPE:
|
|
||||||
stdin_rh, stdin_wh = pipe(overlapped=(False, True), duplex=True)
|
|
||||||
stdin_rfd = msvcrt.open_osfhandle(stdin_rh, os.O_RDONLY)
|
|
||||||
else:
|
|
||||||
stdin_rfd = stdin
|
|
||||||
if stdout == PIPE:
|
|
||||||
stdout_rh, stdout_wh = pipe(overlapped=(True, False))
|
|
||||||
stdout_wfd = msvcrt.open_osfhandle(stdout_wh, 0)
|
|
||||||
else:
|
|
||||||
stdout_wfd = stdout
|
|
||||||
if stderr == PIPE:
|
|
||||||
stderr_rh, stderr_wh = pipe(overlapped=(True, False))
|
|
||||||
stderr_wfd = msvcrt.open_osfhandle(stderr_wh, 0)
|
|
||||||
elif stderr == STDOUT:
|
|
||||||
stderr_wfd = stdout_wfd
|
|
||||||
else:
|
|
||||||
stderr_wfd = stderr
|
|
||||||
try:
|
|
||||||
super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
|
|
||||||
stderr=stderr_wfd, **kwds)
|
|
||||||
except:
|
|
||||||
for h in (stdin_wh, stdout_rh, stderr_rh):
|
|
||||||
if h is not None:
|
|
||||||
_winapi.CloseHandle(h)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
if stdin_wh is not None:
|
|
||||||
self.stdin = PipeHandle(stdin_wh)
|
|
||||||
if stdout_rh is not None:
|
|
||||||
self.stdout = PipeHandle(stdout_rh)
|
|
||||||
if stderr_rh is not None:
|
|
||||||
self.stderr = PipeHandle(stderr_rh)
|
|
||||||
finally:
|
|
||||||
if stdin == PIPE:
|
|
||||||
os.close(stdin_rfd)
|
|
||||||
if stdout == PIPE:
|
|
||||||
os.close(stdout_wfd)
|
|
||||||
if stderr == PIPE:
|
|
||||||
os.close(stderr_wfd)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Dummy implementation of atexit
|
|
||||||
|
|
||||||
|
|
||||||
def register(func, *args, **kwargs):
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
def unregister(func):
|
|
||||||
pass
|
|
||||||
595
Lib/base64.py
595
Lib/base64.py
@@ -1,595 +0,0 @@
|
|||||||
#! /usr/bin/python3.6
|
|
||||||
|
|
||||||
"""Base16, Base32, Base64 (RFC 3548), Base85 and Ascii85 data encodings"""
|
|
||||||
|
|
||||||
# Modified 04-Oct-1995 by Jack Jansen to use binascii module
|
|
||||||
# Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
|
|
||||||
# Modified 22-May-2007 by Guido van Rossum to use bytes everywhere
|
|
||||||
|
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
# Legacy interface exports traditional RFC 2045 Base64 encodings
|
|
||||||
'encode', 'decode', 'encodebytes', 'decodebytes',
|
|
||||||
# Generalized interface for other encodings
|
|
||||||
'b64encode', 'b64decode', 'b32encode', 'b32decode',
|
|
||||||
'b16encode', 'b16decode',
|
|
||||||
# Base85 and Ascii85 encodings
|
|
||||||
'b85encode', 'b85decode', 'a85encode', 'a85decode',
|
|
||||||
# Standard Base64 encoding
|
|
||||||
'standard_b64encode', 'standard_b64decode',
|
|
||||||
# Some common Base64 alternatives. As referenced by RFC 3458, see thread
|
|
||||||
# starting at:
|
|
||||||
#
|
|
||||||
# http://zgp.org/pipermail/p2p-hackers/2001-September/000316.html
|
|
||||||
'urlsafe_b64encode', 'urlsafe_b64decode',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
bytes_types = (bytes, bytearray) # Types acceptable as binary data
|
|
||||||
|
|
||||||
def _bytes_from_decode_data(s):
|
|
||||||
if isinstance(s, str):
|
|
||||||
try:
|
|
||||||
return s.encode('ascii')
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
raise ValueError('string argument should contain only ASCII characters')
|
|
||||||
if isinstance(s, bytes_types):
|
|
||||||
return s
|
|
||||||
try:
|
|
||||||
return memoryview(s).tobytes()
|
|
||||||
except TypeError:
|
|
||||||
raise TypeError("argument should be a bytes-like object or ASCII "
|
|
||||||
"string, not %r" % s.__class__.__name__) from None
|
|
||||||
|
|
||||||
|
|
||||||
# Base64 encoding/decoding uses binascii
|
|
||||||
|
|
||||||
def b64encode(s, altchars=None):
|
|
||||||
"""Encode the bytes-like object s using Base64 and return a bytes object.
|
|
||||||
|
|
||||||
Optional altchars should be a byte string of length 2 which specifies an
|
|
||||||
alternative alphabet for the '+' and '/' characters. This allows an
|
|
||||||
application to e.g. generate url or filesystem safe Base64 strings.
|
|
||||||
"""
|
|
||||||
encoded = binascii.b2a_base64(s, newline=False)
|
|
||||||
if altchars is not None:
|
|
||||||
assert len(altchars) == 2, repr(altchars)
|
|
||||||
return encoded.translate(bytes.maketrans(b'+/', altchars))
|
|
||||||
return encoded
|
|
||||||
|
|
||||||
|
|
||||||
def b64decode(s, altchars=None, validate=False):
|
|
||||||
"""Decode the Base64 encoded bytes-like object or ASCII string s.
|
|
||||||
|
|
||||||
Optional altchars must be a bytes-like object or ASCII string of length 2
|
|
||||||
which specifies the alternative alphabet used instead of the '+' and '/'
|
|
||||||
characters.
|
|
||||||
|
|
||||||
The result is returned as a bytes object. A binascii.Error is raised if
|
|
||||||
s is incorrectly padded.
|
|
||||||
|
|
||||||
If validate is False (the default), characters that are neither in the
|
|
||||||
normal base-64 alphabet nor the alternative alphabet are discarded prior
|
|
||||||
to the padding check. If validate is True, these non-alphabet characters
|
|
||||||
in the input result in a binascii.Error.
|
|
||||||
"""
|
|
||||||
s = _bytes_from_decode_data(s)
|
|
||||||
if altchars is not None:
|
|
||||||
altchars = _bytes_from_decode_data(altchars)
|
|
||||||
assert len(altchars) == 2, repr(altchars)
|
|
||||||
s = s.translate(bytes.maketrans(altchars, b'+/'))
|
|
||||||
if validate and not re.match(b'^[A-Za-z0-9+/]*={0,2}$', s):
|
|
||||||
raise binascii.Error('Non-base64 digit found')
|
|
||||||
return binascii.a2b_base64(s)
|
|
||||||
|
|
||||||
|
|
||||||
def standard_b64encode(s):
|
|
||||||
"""Encode bytes-like object s using the standard Base64 alphabet.
|
|
||||||
|
|
||||||
The result is returned as a bytes object.
|
|
||||||
"""
|
|
||||||
return b64encode(s)
|
|
||||||
|
|
||||||
def standard_b64decode(s):
|
|
||||||
"""Decode bytes encoded with the standard Base64 alphabet.
|
|
||||||
|
|
||||||
Argument s is a bytes-like object or ASCII string to decode. The result
|
|
||||||
is returned as a bytes object. A binascii.Error is raised if the input
|
|
||||||
is incorrectly padded. Characters that are not in the standard alphabet
|
|
||||||
are discarded prior to the padding check.
|
|
||||||
"""
|
|
||||||
return b64decode(s)
|
|
||||||
|
|
||||||
|
|
||||||
_urlsafe_encode_translation = bytes.maketrans(b'+/', b'-_')
|
|
||||||
_urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/')
|
|
||||||
|
|
||||||
def urlsafe_b64encode(s):
|
|
||||||
"""Encode bytes using the URL- and filesystem-safe Base64 alphabet.
|
|
||||||
|
|
||||||
Argument s is a bytes-like object to encode. The result is returned as a
|
|
||||||
bytes object. The alphabet uses '-' instead of '+' and '_' instead of
|
|
||||||
'/'.
|
|
||||||
"""
|
|
||||||
return b64encode(s).translate(_urlsafe_encode_translation)
|
|
||||||
|
|
||||||
def urlsafe_b64decode(s):
|
|
||||||
"""Decode bytes using the URL- and filesystem-safe Base64 alphabet.
|
|
||||||
|
|
||||||
Argument s is a bytes-like object or ASCII string to decode. The result
|
|
||||||
is returned as a bytes object. A binascii.Error is raised if the input
|
|
||||||
is incorrectly padded. Characters that are not in the URL-safe base-64
|
|
||||||
alphabet, and are not a plus '+' or slash '/', are discarded prior to the
|
|
||||||
padding check.
|
|
||||||
|
|
||||||
The alphabet uses '-' instead of '+' and '_' instead of '/'.
|
|
||||||
"""
|
|
||||||
s = _bytes_from_decode_data(s)
|
|
||||||
s = s.translate(_urlsafe_decode_translation)
|
|
||||||
return b64decode(s)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Base32 encoding/decoding must be done in Python
|
|
||||||
_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
||||||
_b32tab2 = None
|
|
||||||
_b32rev = None
|
|
||||||
|
|
||||||
def b32encode(s):
|
|
||||||
"""Encode the bytes-like object s using Base32 and return a bytes object.
|
|
||||||
"""
|
|
||||||
global _b32tab2
|
|
||||||
# Delay the initialization of the table to not waste memory
|
|
||||||
# if the function is never called
|
|
||||||
if _b32tab2 is None:
|
|
||||||
b32tab = [bytes((i,)) for i in _b32alphabet]
|
|
||||||
_b32tab2 = [a + b for a in b32tab for b in b32tab]
|
|
||||||
b32tab = None
|
|
||||||
|
|
||||||
if not isinstance(s, bytes_types):
|
|
||||||
s = memoryview(s).tobytes()
|
|
||||||
leftover = len(s) % 5
|
|
||||||
# Pad the last quantum with zero bits if necessary
|
|
||||||
if leftover:
|
|
||||||
s = s + b'\0' * (5 - leftover) # Don't use += !
|
|
||||||
encoded = bytearray()
|
|
||||||
from_bytes = int.from_bytes
|
|
||||||
b32tab2 = _b32tab2
|
|
||||||
for i in range(0, len(s), 5):
|
|
||||||
c = from_bytes(s[i: i + 5], 'big')
|
|
||||||
encoded += (b32tab2[c >> 30] + # bits 1 - 10
|
|
||||||
b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
|
|
||||||
b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
|
|
||||||
b32tab2[c & 0x3ff] # bits 31 - 40
|
|
||||||
)
|
|
||||||
# Adjust for any leftover partial quanta
|
|
||||||
if leftover == 1:
|
|
||||||
encoded[-6:] = b'======'
|
|
||||||
elif leftover == 2:
|
|
||||||
encoded[-4:] = b'===='
|
|
||||||
elif leftover == 3:
|
|
||||||
encoded[-3:] = b'==='
|
|
||||||
elif leftover == 4:
|
|
||||||
encoded[-1:] = b'='
|
|
||||||
return bytes(encoded)
|
|
||||||
|
|
||||||
def b32decode(s, casefold=False, map01=None):
|
|
||||||
"""Decode the Base32 encoded bytes-like object or ASCII string s.
|
|
||||||
|
|
||||||
Optional casefold is a flag specifying whether a lowercase alphabet is
|
|
||||||
acceptable as input. For security purposes, the default is False.
|
|
||||||
|
|
||||||
RFC 3548 allows for optional mapping of the digit 0 (zero) to the
|
|
||||||
letter O (oh), and for optional mapping of the digit 1 (one) to
|
|
||||||
either the letter I (eye) or letter L (el). The optional argument
|
|
||||||
map01 when not None, specifies which letter the digit 1 should be
|
|
||||||
mapped to (when map01 is not None, the digit 0 is always mapped to
|
|
||||||
the letter O). For security purposes the default is None, so that
|
|
||||||
0 and 1 are not allowed in the input.
|
|
||||||
|
|
||||||
The result is returned as a bytes object. A binascii.Error is raised if
|
|
||||||
the input is incorrectly padded or if there are non-alphabet
|
|
||||||
characters present in the input.
|
|
||||||
"""
|
|
||||||
global _b32rev
|
|
||||||
# Delay the initialization of the table to not waste memory
|
|
||||||
# if the function is never called
|
|
||||||
if _b32rev is None:
|
|
||||||
_b32rev = {v: k for k, v in enumerate(_b32alphabet)}
|
|
||||||
s = _bytes_from_decode_data(s)
|
|
||||||
if len(s) % 8:
|
|
||||||
raise binascii.Error('Incorrect padding')
|
|
||||||
# Handle section 2.4 zero and one mapping. The flag map01 will be either
|
|
||||||
# False, or the character to map the digit 1 (one) to. It should be
|
|
||||||
# either L (el) or I (eye).
|
|
||||||
if map01 is not None:
|
|
||||||
map01 = _bytes_from_decode_data(map01)
|
|
||||||
assert len(map01) == 1, repr(map01)
|
|
||||||
s = s.translate(bytes.maketrans(b'01', b'O' + map01))
|
|
||||||
if casefold:
|
|
||||||
s = s.upper()
|
|
||||||
# Strip off pad characters from the right. We need to count the pad
|
|
||||||
# characters because this will tell us how many null bytes to remove from
|
|
||||||
# the end of the decoded string.
|
|
||||||
l = len(s)
|
|
||||||
s = s.rstrip(b'=')
|
|
||||||
padchars = l - len(s)
|
|
||||||
# Now decode the full quanta
|
|
||||||
decoded = bytearray()
|
|
||||||
b32rev = _b32rev
|
|
||||||
for i in range(0, len(s), 8):
|
|
||||||
quanta = s[i: i + 8]
|
|
||||||
acc = 0
|
|
||||||
try:
|
|
||||||
for c in quanta:
|
|
||||||
acc = (acc << 5) + b32rev[c]
|
|
||||||
except KeyError:
|
|
||||||
raise binascii.Error('Non-base32 digit found') from None
|
|
||||||
decoded += acc.to_bytes(5, 'big')
|
|
||||||
# Process the last, partial quanta
|
|
||||||
if l % 8 or padchars not in {0, 1, 3, 4, 6}:
|
|
||||||
raise binascii.Error('Incorrect padding')
|
|
||||||
if padchars and decoded:
|
|
||||||
acc <<= 5 * padchars
|
|
||||||
last = acc.to_bytes(5, 'big')
|
|
||||||
leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1
|
|
||||||
decoded[-5:] = last[:leftover]
|
|
||||||
return bytes(decoded)
|
|
||||||
|
|
||||||
|
|
||||||
# RFC 3548, Base 16 Alphabet specifies uppercase, but hexlify() returns
|
|
||||||
# lowercase. The RFC also recommends against accepting input case
|
|
||||||
# insensitively.
|
|
||||||
def b16encode(s):
|
|
||||||
"""Encode the bytes-like object s using Base16 and return a bytes object.
|
|
||||||
"""
|
|
||||||
return binascii.hexlify(s).upper()
|
|
||||||
|
|
||||||
|
|
||||||
def b16decode(s, casefold=False):
|
|
||||||
"""Decode the Base16 encoded bytes-like object or ASCII string s.
|
|
||||||
|
|
||||||
Optional casefold is a flag specifying whether a lowercase alphabet is
|
|
||||||
acceptable as input. For security purposes, the default is False.
|
|
||||||
|
|
||||||
The result is returned as a bytes object. A binascii.Error is raised if
|
|
||||||
s is incorrectly padded or if there are non-alphabet characters present
|
|
||||||
in the input.
|
|
||||||
"""
|
|
||||||
s = _bytes_from_decode_data(s)
|
|
||||||
if casefold:
|
|
||||||
s = s.upper()
|
|
||||||
if re.search(b'[^0-9A-F]', s):
|
|
||||||
raise binascii.Error('Non-base16 digit found')
|
|
||||||
return binascii.unhexlify(s)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Ascii85 encoding/decoding
|
|
||||||
#
|
|
||||||
|
|
||||||
_a85chars = None
|
|
||||||
_a85chars2 = None
|
|
||||||
_A85START = b"<~"
|
|
||||||
_A85END = b"~>"
|
|
||||||
|
|
||||||
def _85encode(b, chars, chars2, pad=False, foldnuls=False, foldspaces=False):
|
|
||||||
# Helper function for a85encode and b85encode
|
|
||||||
if not isinstance(b, bytes_types):
|
|
||||||
b = memoryview(b).tobytes()
|
|
||||||
|
|
||||||
padding = (-len(b)) % 4
|
|
||||||
if padding:
|
|
||||||
b = b + b'\0' * padding
|
|
||||||
words = struct.Struct('!%dI' % (len(b) // 4)).unpack(b)
|
|
||||||
|
|
||||||
chunks = [b'z' if foldnuls and not word else
|
|
||||||
b'y' if foldspaces and word == 0x20202020 else
|
|
||||||
(chars2[word // 614125] +
|
|
||||||
chars2[word // 85 % 7225] +
|
|
||||||
chars[word % 85])
|
|
||||||
for word in words]
|
|
||||||
|
|
||||||
if padding and not pad:
|
|
||||||
if chunks[-1] == b'z':
|
|
||||||
chunks[-1] = chars[0] * 5
|
|
||||||
chunks[-1] = chunks[-1][:-padding]
|
|
||||||
|
|
||||||
return b''.join(chunks)
|
|
||||||
|
|
||||||
def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
|
|
||||||
"""Encode bytes-like object b using Ascii85 and return a bytes object.
|
|
||||||
|
|
||||||
foldspaces is an optional flag that uses the special short sequence 'y'
|
|
||||||
instead of 4 consecutive spaces (ASCII 0x20) as supported by 'btoa'. This
|
|
||||||
feature is not supported by the "standard" Adobe encoding.
|
|
||||||
|
|
||||||
wrapcol controls whether the output should have newline (b'\\n') characters
|
|
||||||
added to it. If this is non-zero, each output line will be at most this
|
|
||||||
many characters long.
|
|
||||||
|
|
||||||
pad controls whether the input is padded to a multiple of 4 before
|
|
||||||
encoding. Note that the btoa implementation always pads.
|
|
||||||
|
|
||||||
adobe controls whether the encoded byte sequence is framed with <~ and ~>,
|
|
||||||
which is used by the Adobe implementation.
|
|
||||||
"""
|
|
||||||
global _a85chars, _a85chars2
|
|
||||||
# Delay the initialization of tables to not waste memory
|
|
||||||
# if the function is never called
|
|
||||||
if _a85chars is None:
|
|
||||||
_a85chars = [bytes((i,)) for i in range(33, 118)]
|
|
||||||
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
|
|
||||||
|
|
||||||
result = _85encode(b, _a85chars, _a85chars2, pad, True, foldspaces)
|
|
||||||
|
|
||||||
if adobe:
|
|
||||||
result = _A85START + result
|
|
||||||
if wrapcol:
|
|
||||||
wrapcol = max(2 if adobe else 1, wrapcol)
|
|
||||||
chunks = [result[i: i + wrapcol]
|
|
||||||
for i in range(0, len(result), wrapcol)]
|
|
||||||
if adobe:
|
|
||||||
if len(chunks[-1]) + 2 > wrapcol:
|
|
||||||
chunks.append(b'')
|
|
||||||
result = b'\n'.join(chunks)
|
|
||||||
if adobe:
|
|
||||||
result += _A85END
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
|
|
||||||
"""Decode the Ascii85 encoded bytes-like object or ASCII string b.
|
|
||||||
|
|
||||||
foldspaces is a flag that specifies whether the 'y' short sequence should be
|
|
||||||
accepted as shorthand for 4 consecutive spaces (ASCII 0x20). This feature is
|
|
||||||
not supported by the "standard" Adobe encoding.
|
|
||||||
|
|
||||||
adobe controls whether the input sequence is in Adobe Ascii85 format (i.e.
|
|
||||||
is framed with <~ and ~>).
|
|
||||||
|
|
||||||
ignorechars should be a byte string containing characters to ignore from the
|
|
||||||
input. This should only contain whitespace characters, and by default
|
|
||||||
contains all whitespace characters in ASCII.
|
|
||||||
|
|
||||||
The result is returned as a bytes object.
|
|
||||||
"""
|
|
||||||
b = _bytes_from_decode_data(b)
|
|
||||||
if adobe:
|
|
||||||
if not b.endswith(_A85END):
|
|
||||||
raise ValueError(
|
|
||||||
"Ascii85 encoded byte sequences must end "
|
|
||||||
"with {!r}".format(_A85END)
|
|
||||||
)
|
|
||||||
if b.startswith(_A85START):
|
|
||||||
b = b[2:-2] # Strip off start/end markers
|
|
||||||
else:
|
|
||||||
b = b[:-2]
|
|
||||||
#
|
|
||||||
# We have to go through this stepwise, so as to ignore spaces and handle
|
|
||||||
# special short sequences
|
|
||||||
#
|
|
||||||
packI = struct.Struct('!I').pack
|
|
||||||
decoded = []
|
|
||||||
decoded_append = decoded.append
|
|
||||||
curr = []
|
|
||||||
curr_append = curr.append
|
|
||||||
curr_clear = curr.clear
|
|
||||||
for x in b + b'u' * 4:
|
|
||||||
if b'!'[0] <= x <= b'u'[0]:
|
|
||||||
curr_append(x)
|
|
||||||
if len(curr) == 5:
|
|
||||||
acc = 0
|
|
||||||
for x in curr:
|
|
||||||
acc = 85 * acc + (x - 33)
|
|
||||||
try:
|
|
||||||
decoded_append(packI(acc))
|
|
||||||
except struct.error:
|
|
||||||
raise ValueError('Ascii85 overflow') from None
|
|
||||||
curr_clear()
|
|
||||||
elif x == b'z'[0]:
|
|
||||||
if curr:
|
|
||||||
raise ValueError('z inside Ascii85 5-tuple')
|
|
||||||
decoded_append(b'\0\0\0\0')
|
|
||||||
elif foldspaces and x == b'y'[0]:
|
|
||||||
if curr:
|
|
||||||
raise ValueError('y inside Ascii85 5-tuple')
|
|
||||||
decoded_append(b'\x20\x20\x20\x20')
|
|
||||||
elif x in ignorechars:
|
|
||||||
# Skip whitespace
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise ValueError('Non-Ascii85 digit found: %c' % x)
|
|
||||||
|
|
||||||
result = b''.join(decoded)
|
|
||||||
padding = 4 - len(curr)
|
|
||||||
if padding:
|
|
||||||
# Throw away the extra padding
|
|
||||||
result = result[:-padding]
|
|
||||||
return result
|
|
||||||
|
|
||||||
# The following code is originally taken (with permission) from Mercurial
|
|
||||||
|
|
||||||
_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
|
|
||||||
_b85chars = None
|
|
||||||
_b85chars2 = None
|
|
||||||
_b85dec = None
|
|
||||||
|
|
||||||
def b85encode(b, pad=False):
|
|
||||||
"""Encode bytes-like object b in base85 format and return a bytes object.
|
|
||||||
|
|
||||||
If pad is true, the input is padded with b'\\0' so its length is a multiple of
|
|
||||||
4 bytes before encoding.
|
|
||||||
"""
|
|
||||||
global _b85chars, _b85chars2
|
|
||||||
# Delay the initialization of tables to not waste memory
|
|
||||||
# if the function is never called
|
|
||||||
if _b85chars is None:
|
|
||||||
_b85chars = [bytes((i,)) for i in _b85alphabet]
|
|
||||||
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
|
|
||||||
return _85encode(b, _b85chars, _b85chars2, pad)
|
|
||||||
|
|
||||||
def b85decode(b):
|
|
||||||
"""Decode the base85-encoded bytes-like object or ASCII string b
|
|
||||||
|
|
||||||
The result is returned as a bytes object.
|
|
||||||
"""
|
|
||||||
global _b85dec
|
|
||||||
# Delay the initialization of tables to not waste memory
|
|
||||||
# if the function is never called
|
|
||||||
if _b85dec is None:
|
|
||||||
_b85dec = [None] * 256
|
|
||||||
for i, c in enumerate(_b85alphabet):
|
|
||||||
_b85dec[c] = i
|
|
||||||
|
|
||||||
b = _bytes_from_decode_data(b)
|
|
||||||
padding = (-len(b)) % 5
|
|
||||||
b = b + b'~' * padding
|
|
||||||
out = []
|
|
||||||
packI = struct.Struct('!I').pack
|
|
||||||
for i in range(0, len(b), 5):
|
|
||||||
chunk = b[i:i + 5]
|
|
||||||
acc = 0
|
|
||||||
try:
|
|
||||||
for c in chunk:
|
|
||||||
acc = acc * 85 + _b85dec[c]
|
|
||||||
except TypeError:
|
|
||||||
for j, c in enumerate(chunk):
|
|
||||||
if _b85dec[c] is None:
|
|
||||||
raise ValueError('bad base85 character at position %d'
|
|
||||||
% (i + j)) from None
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
out.append(packI(acc))
|
|
||||||
except struct.error:
|
|
||||||
raise ValueError('base85 overflow in hunk starting at byte %d'
|
|
||||||
% i) from None
|
|
||||||
|
|
||||||
result = b''.join(out)
|
|
||||||
if padding:
|
|
||||||
result = result[:-padding]
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Legacy interface. This code could be cleaned up since I don't believe
|
|
||||||
# binascii has any line length limitations. It just doesn't seem worth it
|
|
||||||
# though. The files should be opened in binary mode.
|
|
||||||
|
|
||||||
MAXLINESIZE = 76 # Excluding the CRLF
|
|
||||||
MAXBINSIZE = (MAXLINESIZE//4)*3
|
|
||||||
|
|
||||||
def encode(input, output):
|
|
||||||
"""Encode a file; input and output are binary files."""
|
|
||||||
while True:
|
|
||||||
s = input.read(MAXBINSIZE)
|
|
||||||
if not s:
|
|
||||||
break
|
|
||||||
while len(s) < MAXBINSIZE:
|
|
||||||
ns = input.read(MAXBINSIZE-len(s))
|
|
||||||
if not ns:
|
|
||||||
break
|
|
||||||
s += ns
|
|
||||||
line = binascii.b2a_base64(s)
|
|
||||||
output.write(line)
|
|
||||||
|
|
||||||
|
|
||||||
def decode(input, output):
|
|
||||||
"""Decode a file; input and output are binary files."""
|
|
||||||
while True:
|
|
||||||
line = input.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
s = binascii.a2b_base64(line)
|
|
||||||
output.write(s)
|
|
||||||
|
|
||||||
def _input_type_check(s):
|
|
||||||
try:
|
|
||||||
m = memoryview(s)
|
|
||||||
except TypeError as err:
|
|
||||||
msg = "expected bytes-like object, not %s" % s.__class__.__name__
|
|
||||||
raise TypeError(msg) from err
|
|
||||||
if m.format not in ('c', 'b', 'B'):
|
|
||||||
msg = ("expected single byte elements, not %r from %s" %
|
|
||||||
(m.format, s.__class__.__name__))
|
|
||||||
raise TypeError(msg)
|
|
||||||
if m.ndim != 1:
|
|
||||||
msg = ("expected 1-D data, not %d-D data from %s" %
|
|
||||||
(m.ndim, s.__class__.__name__))
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def encodebytes(s):
|
|
||||||
"""Encode a bytestring into a bytes object containing multiple lines
|
|
||||||
of base-64 data."""
|
|
||||||
_input_type_check(s)
|
|
||||||
pieces = []
|
|
||||||
for i in range(0, len(s), MAXBINSIZE):
|
|
||||||
chunk = s[i : i + MAXBINSIZE]
|
|
||||||
pieces.append(binascii.b2a_base64(chunk))
|
|
||||||
return b"".join(pieces)
|
|
||||||
|
|
||||||
def encodestring(s):
|
|
||||||
"""Legacy alias of encodebytes()."""
|
|
||||||
import warnings
|
|
||||||
warnings.warn("encodestring() is a deprecated alias since 3.1, "
|
|
||||||
"use encodebytes()",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
return encodebytes(s)
|
|
||||||
|
|
||||||
|
|
||||||
def decodebytes(s):
|
|
||||||
"""Decode a bytestring of base-64 data into a bytes object."""
|
|
||||||
_input_type_check(s)
|
|
||||||
return binascii.a2b_base64(s)
|
|
||||||
|
|
||||||
def decodestring(s):
|
|
||||||
"""Legacy alias of decodebytes()."""
|
|
||||||
import warnings
|
|
||||||
warnings.warn("decodestring() is a deprecated alias since Python 3.1, "
|
|
||||||
"use decodebytes()",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
return decodebytes(s)
|
|
||||||
|
|
||||||
|
|
||||||
# Usable as a script...
|
|
||||||
def main():
|
|
||||||
"""Small main program"""
|
|
||||||
import sys, getopt
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'deut')
|
|
||||||
except getopt.error as msg:
|
|
||||||
sys.stdout = sys.stderr
|
|
||||||
print(msg)
|
|
||||||
print("""usage: %s [-d|-e|-u|-t] [file|-]
|
|
||||||
-d, -u: decode
|
|
||||||
-e: encode (default)
|
|
||||||
-t: encode and decode string 'Aladdin:open sesame'"""%sys.argv[0])
|
|
||||||
sys.exit(2)
|
|
||||||
func = encode
|
|
||||||
for o, a in opts:
|
|
||||||
if o == '-e': func = encode
|
|
||||||
if o == '-d': func = decode
|
|
||||||
if o == '-u': func = decode
|
|
||||||
if o == '-t': test(); return
|
|
||||||
if args and args[0] != '-':
|
|
||||||
with open(args[0], 'rb') as f:
|
|
||||||
func(f, sys.stdout.buffer)
|
|
||||||
else:
|
|
||||||
func(sys.stdin.buffer, sys.stdout.buffer)
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
s0 = b"Aladdin:open sesame"
|
|
||||||
print(repr(s0))
|
|
||||||
s1 = encodebytes(s0)
|
|
||||||
print(repr(s1))
|
|
||||||
s2 = decodebytes(s1)
|
|
||||||
print(repr(s2))
|
|
||||||
assert s0 == s2
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
880
Lib/bdb.py
880
Lib/bdb.py
@@ -1,880 +0,0 @@
|
|||||||
"""Debugger basics"""
|
|
||||||
|
|
||||||
import fnmatch
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR
|
|
||||||
|
|
||||||
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
|
|
||||||
|
|
||||||
GENERATOR_AND_COROUTINE_FLAGS = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR
|
|
||||||
|
|
||||||
|
|
||||||
class BdbQuit(Exception):
|
|
||||||
"""Exception to give up completely."""
|
|
||||||
|
|
||||||
|
|
||||||
class Bdb:
|
|
||||||
"""Generic Python debugger base class.
|
|
||||||
|
|
||||||
This class takes care of details of the trace facility;
|
|
||||||
a derived class should implement user interaction.
|
|
||||||
The standard debugger class (pdb.Pdb) is an example.
|
|
||||||
|
|
||||||
The optional skip argument must be an iterable of glob-style
|
|
||||||
module name patterns. The debugger will not step into frames
|
|
||||||
that originate in a module that matches one of these patterns.
|
|
||||||
Whether a frame is considered to originate in a certain module
|
|
||||||
is determined by the __name__ in the frame globals.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, skip=None):
|
|
||||||
self.skip = set(skip) if skip else None
|
|
||||||
self.breaks = {}
|
|
||||||
self.fncache = {}
|
|
||||||
self.frame_returning = None
|
|
||||||
|
|
||||||
def canonic(self, filename):
|
|
||||||
"""Return canonical form of filename.
|
|
||||||
|
|
||||||
For real filenames, the canonical form is a case-normalized (on
|
|
||||||
case insensitive filesystems) absolute path. 'Filenames' with
|
|
||||||
angle brackets, such as "<stdin>", generated in interactive
|
|
||||||
mode, are returned unchanged.
|
|
||||||
"""
|
|
||||||
if filename == "<" + filename[1:-1] + ">":
|
|
||||||
return filename
|
|
||||||
canonic = self.fncache.get(filename)
|
|
||||||
if not canonic:
|
|
||||||
canonic = os.path.abspath(filename)
|
|
||||||
canonic = os.path.normcase(canonic)
|
|
||||||
self.fncache[filename] = canonic
|
|
||||||
return canonic
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""Set values of attributes as ready to start debugging."""
|
|
||||||
import linecache
|
|
||||||
linecache.checkcache()
|
|
||||||
self.botframe = None
|
|
||||||
self._set_stopinfo(None, None)
|
|
||||||
|
|
||||||
def trace_dispatch(self, frame, event, arg):
|
|
||||||
"""Dispatch a trace function for debugged frames based on the event.
|
|
||||||
|
|
||||||
This function is installed as the trace function for debugged
|
|
||||||
frames. Its return value is the new trace function, which is
|
|
||||||
usually itself. The default implementation decides how to
|
|
||||||
dispatch a frame, depending on the type of event (passed in as a
|
|
||||||
string) that is about to be executed.
|
|
||||||
|
|
||||||
The event can be one of the following:
|
|
||||||
line: A new line of code is going to be executed.
|
|
||||||
call: A function is about to be called or another code block
|
|
||||||
is entered.
|
|
||||||
return: A function or other code block is about to return.
|
|
||||||
exception: An exception has occurred.
|
|
||||||
c_call: A C function is about to be called.
|
|
||||||
c_return: A C function has returned.
|
|
||||||
c_exception: A C function has raised an exception.
|
|
||||||
|
|
||||||
For the Python events, specialized functions (see the dispatch_*()
|
|
||||||
methods) are called. For the C events, no action is taken.
|
|
||||||
|
|
||||||
The arg parameter depends on the previous event.
|
|
||||||
"""
|
|
||||||
if self.quitting:
|
|
||||||
return # None
|
|
||||||
if event == 'line':
|
|
||||||
return self.dispatch_line(frame)
|
|
||||||
if event == 'call':
|
|
||||||
return self.dispatch_call(frame, arg)
|
|
||||||
if event == 'return':
|
|
||||||
return self.dispatch_return(frame, arg)
|
|
||||||
if event == 'exception':
|
|
||||||
return self.dispatch_exception(frame, arg)
|
|
||||||
if event == 'c_call':
|
|
||||||
return self.trace_dispatch
|
|
||||||
if event == 'c_exception':
|
|
||||||
return self.trace_dispatch
|
|
||||||
if event == 'c_return':
|
|
||||||
return self.trace_dispatch
|
|
||||||
print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
|
|
||||||
return self.trace_dispatch
|
|
||||||
|
|
||||||
def dispatch_line(self, frame):
|
|
||||||
"""Invoke user function and return trace function for line event.
|
|
||||||
|
|
||||||
If the debugger stops on the current line, invoke
|
|
||||||
self.user_line(). Raise BdbQuit if self.quitting is set.
|
|
||||||
Return self.trace_dispatch to continue tracing in this scope.
|
|
||||||
"""
|
|
||||||
if self.stop_here(frame) or self.break_here(frame):
|
|
||||||
self.user_line(frame)
|
|
||||||
if self.quitting: raise BdbQuit
|
|
||||||
return self.trace_dispatch
|
|
||||||
|
|
||||||
def dispatch_call(self, frame, arg):
|
|
||||||
"""Invoke user function and return trace function for call event.
|
|
||||||
|
|
||||||
If the debugger stops on this function call, invoke
|
|
||||||
self.user_call(). Raise BbdQuit if self.quitting is set.
|
|
||||||
Return self.trace_dispatch to continue tracing in this scope.
|
|
||||||
"""
|
|
||||||
# XXX 'arg' is no longer used
|
|
||||||
if self.botframe is None:
|
|
||||||
# First call of dispatch since reset()
|
|
||||||
self.botframe = frame.f_back # (CT) Note that this may also be None!
|
|
||||||
return self.trace_dispatch
|
|
||||||
if not (self.stop_here(frame) or self.break_anywhere(frame)):
|
|
||||||
# No need to trace this function
|
|
||||||
return # None
|
|
||||||
# Ignore call events in generator except when stepping.
|
|
||||||
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
|
|
||||||
return self.trace_dispatch
|
|
||||||
self.user_call(frame, arg)
|
|
||||||
if self.quitting: raise BdbQuit
|
|
||||||
return self.trace_dispatch
|
|
||||||
|
|
||||||
def dispatch_return(self, frame, arg):
|
|
||||||
"""Invoke user function and return trace function for return event.
|
|
||||||
|
|
||||||
If the debugger stops on this function return, invoke
|
|
||||||
self.user_return(). Raise BdbQuit if self.quitting is set.
|
|
||||||
Return self.trace_dispatch to continue tracing in this scope.
|
|
||||||
"""
|
|
||||||
if self.stop_here(frame) or frame == self.returnframe:
|
|
||||||
# Ignore return events in generator except when stepping.
|
|
||||||
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
|
|
||||||
return self.trace_dispatch
|
|
||||||
try:
|
|
||||||
self.frame_returning = frame
|
|
||||||
self.user_return(frame, arg)
|
|
||||||
finally:
|
|
||||||
self.frame_returning = None
|
|
||||||
if self.quitting: raise BdbQuit
|
|
||||||
# The user issued a 'next' or 'until' command.
|
|
||||||
if self.stopframe is frame and self.stoplineno != -1:
|
|
||||||
self._set_stopinfo(None, None)
|
|
||||||
return self.trace_dispatch
|
|
||||||
|
|
||||||
def dispatch_exception(self, frame, arg):
|
|
||||||
"""Invoke user function and return trace function for exception event.
|
|
||||||
|
|
||||||
If the debugger stops on this exception, invoke
|
|
||||||
self.user_exception(). Raise BdbQuit if self.quitting is set.
|
|
||||||
Return self.trace_dispatch to continue tracing in this scope.
|
|
||||||
"""
|
|
||||||
if self.stop_here(frame):
|
|
||||||
# When stepping with next/until/return in a generator frame, skip
|
|
||||||
# the internal StopIteration exception (with no traceback)
|
|
||||||
# triggered by a subiterator run with the 'yield from' statement.
|
|
||||||
if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
|
||||||
and arg[0] is StopIteration and arg[2] is None):
|
|
||||||
self.user_exception(frame, arg)
|
|
||||||
if self.quitting: raise BdbQuit
|
|
||||||
# Stop at the StopIteration or GeneratorExit exception when the user
|
|
||||||
# has set stopframe in a generator by issuing a return command, or a
|
|
||||||
# next/until command at the last statement in the generator before the
|
|
||||||
# exception.
|
|
||||||
elif (self.stopframe and frame is not self.stopframe
|
|
||||||
and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
|
|
||||||
and arg[0] in (StopIteration, GeneratorExit)):
|
|
||||||
self.user_exception(frame, arg)
|
|
||||||
if self.quitting: raise BdbQuit
|
|
||||||
|
|
||||||
return self.trace_dispatch
|
|
||||||
|
|
||||||
# Normally derived classes don't override the following
|
|
||||||
# methods, but they may if they want to redefine the
|
|
||||||
# definition of stopping and breakpoints.
|
|
||||||
|
|
||||||
def is_skipped_module(self, module_name):
|
|
||||||
"Return True if module_name matches any skip pattern."
|
|
||||||
if module_name is None: # some modules do not have names
|
|
||||||
return False
|
|
||||||
for pattern in self.skip:
|
|
||||||
if fnmatch.fnmatch(module_name, pattern):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def stop_here(self, frame):
|
|
||||||
"Return True if frame is below the starting frame in the stack."
|
|
||||||
# (CT) stopframe may now also be None, see dispatch_call.
|
|
||||||
# (CT) the former test for None is therefore removed from here.
|
|
||||||
if self.skip and \
|
|
||||||
self.is_skipped_module(frame.f_globals.get('__name__')):
|
|
||||||
return False
|
|
||||||
if frame is self.stopframe:
|
|
||||||
if self.stoplineno == -1:
|
|
||||||
return False
|
|
||||||
return frame.f_lineno >= self.stoplineno
|
|
||||||
if not self.stopframe:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def break_here(self, frame):
|
|
||||||
"""Return True if there is an effective breakpoint for this line.
|
|
||||||
|
|
||||||
Check for line or function breakpoint and if in effect.
|
|
||||||
Delete temporary breakpoints if effective() says to.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(frame.f_code.co_filename)
|
|
||||||
if filename not in self.breaks:
|
|
||||||
return False
|
|
||||||
lineno = frame.f_lineno
|
|
||||||
if lineno not in self.breaks[filename]:
|
|
||||||
# The line itself has no breakpoint, but maybe the line is the
|
|
||||||
# first line of a function with breakpoint set by function name.
|
|
||||||
lineno = frame.f_code.co_firstlineno
|
|
||||||
if lineno not in self.breaks[filename]:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# flag says ok to delete temp. bp
|
|
||||||
(bp, flag) = effective(filename, lineno, frame)
|
|
||||||
if bp:
|
|
||||||
self.currentbp = bp.number
|
|
||||||
if (flag and bp.temporary):
|
|
||||||
self.do_clear(str(bp.number))
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_clear(self, arg):
|
|
||||||
"""Remove temporary breakpoint.
|
|
||||||
|
|
||||||
Must implement in derived classes or get NotImplementedError.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError("subclass of bdb must implement do_clear()")
|
|
||||||
|
|
||||||
def break_anywhere(self, frame):
|
|
||||||
"""Return True if there is any breakpoint for frame's filename.
|
|
||||||
"""
|
|
||||||
return self.canonic(frame.f_code.co_filename) in self.breaks
|
|
||||||
|
|
||||||
# Derived classes should override the user_* methods
|
|
||||||
# to gain control.
|
|
||||||
|
|
||||||
def user_call(self, frame, argument_list):
|
|
||||||
"""Called if we might stop in a function."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def user_line(self, frame):
|
|
||||||
"""Called when we stop or break at a line."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def user_return(self, frame, return_value):
|
|
||||||
"""Called when a return trap is set here."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def user_exception(self, frame, exc_info):
|
|
||||||
"""Called when we stop on an exception."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
|
|
||||||
"""Set the attributes for stopping.
|
|
||||||
|
|
||||||
If stoplineno is greater than or equal to 0, then stop at line
|
|
||||||
greater than or equal to the stopline. If stoplineno is -1, then
|
|
||||||
don't stop at all.
|
|
||||||
"""
|
|
||||||
self.stopframe = stopframe
|
|
||||||
self.returnframe = returnframe
|
|
||||||
self.quitting = False
|
|
||||||
# stoplineno >= 0 means: stop at line >= the stoplineno
|
|
||||||
# stoplineno -1 means: don't stop at all
|
|
||||||
self.stoplineno = stoplineno
|
|
||||||
|
|
||||||
# Derived classes and clients can call the following methods
|
|
||||||
# to affect the stepping state.
|
|
||||||
|
|
||||||
def set_until(self, frame, lineno=None):
|
|
||||||
"""Stop when the line with the lineno greater than the current one is
|
|
||||||
reached or when returning from current frame."""
|
|
||||||
# the name "until" is borrowed from gdb
|
|
||||||
if lineno is None:
|
|
||||||
lineno = frame.f_lineno + 1
|
|
||||||
self._set_stopinfo(frame, frame, lineno)
|
|
||||||
|
|
||||||
def set_step(self):
|
|
||||||
"""Stop after one line of code."""
|
|
||||||
# Issue #13183: pdb skips frames after hitting a breakpoint and running
|
|
||||||
# step commands.
|
|
||||||
# Restore the trace function in the caller (that may not have been set
|
|
||||||
# for performance reasons) when returning from the current frame.
|
|
||||||
if self.frame_returning:
|
|
||||||
caller_frame = self.frame_returning.f_back
|
|
||||||
if caller_frame and not caller_frame.f_trace:
|
|
||||||
caller_frame.f_trace = self.trace_dispatch
|
|
||||||
self._set_stopinfo(None, None)
|
|
||||||
|
|
||||||
def set_next(self, frame):
|
|
||||||
"""Stop on the next line in or below the given frame."""
|
|
||||||
self._set_stopinfo(frame, None)
|
|
||||||
|
|
||||||
def set_return(self, frame):
|
|
||||||
"""Stop when returning from the given frame."""
|
|
||||||
if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
|
|
||||||
self._set_stopinfo(frame, None, -1)
|
|
||||||
else:
|
|
||||||
self._set_stopinfo(frame.f_back, frame)
|
|
||||||
|
|
||||||
def set_trace(self, frame=None):
|
|
||||||
"""Start debugging from frame.
|
|
||||||
|
|
||||||
If frame is not specified, debugging starts from caller's frame.
|
|
||||||
"""
|
|
||||||
if frame is None:
|
|
||||||
frame = sys._getframe().f_back
|
|
||||||
self.reset()
|
|
||||||
while frame:
|
|
||||||
frame.f_trace = self.trace_dispatch
|
|
||||||
self.botframe = frame
|
|
||||||
frame = frame.f_back
|
|
||||||
self.set_step()
|
|
||||||
sys.settrace(self.trace_dispatch)
|
|
||||||
|
|
||||||
def set_continue(self):
|
|
||||||
"""Stop only at breakpoints or when finished.
|
|
||||||
|
|
||||||
If there are no breakpoints, set the system trace function to None.
|
|
||||||
"""
|
|
||||||
# Don't stop except at breakpoints or when finished
|
|
||||||
self._set_stopinfo(self.botframe, None, -1)
|
|
||||||
if not self.breaks:
|
|
||||||
# no breakpoints; run without debugger overhead
|
|
||||||
sys.settrace(None)
|
|
||||||
frame = sys._getframe().f_back
|
|
||||||
while frame and frame is not self.botframe:
|
|
||||||
del frame.f_trace
|
|
||||||
frame = frame.f_back
|
|
||||||
|
|
||||||
def set_quit(self):
|
|
||||||
"""Set quitting attribute to True.
|
|
||||||
|
|
||||||
Raises BdbQuit exception in the next call to a dispatch_*() method.
|
|
||||||
"""
|
|
||||||
self.stopframe = self.botframe
|
|
||||||
self.returnframe = None
|
|
||||||
self.quitting = True
|
|
||||||
sys.settrace(None)
|
|
||||||
|
|
||||||
# Derived classes and clients can call the following methods
|
|
||||||
# to manipulate breakpoints. These methods return an
|
|
||||||
# error message if something went wrong, None if all is well.
|
|
||||||
# Set_break prints out the breakpoint line and file:lineno.
|
|
||||||
# Call self.get_*break*() to see the breakpoints or better
|
|
||||||
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
|
|
||||||
|
|
||||||
def set_break(self, filename, lineno, temporary=False, cond=None,
|
|
||||||
funcname=None):
|
|
||||||
"""Set a new breakpoint for filename:lineno.
|
|
||||||
|
|
||||||
If lineno doesn't exist for the filename, return an error message.
|
|
||||||
The filename should be in canonical form.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
import linecache # Import as late as possible
|
|
||||||
line = linecache.getline(filename, lineno)
|
|
||||||
if not line:
|
|
||||||
return 'Line %s:%d does not exist' % (filename, lineno)
|
|
||||||
list = self.breaks.setdefault(filename, [])
|
|
||||||
if lineno not in list:
|
|
||||||
list.append(lineno)
|
|
||||||
bp = Breakpoint(filename, lineno, temporary, cond, funcname)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _prune_breaks(self, filename, lineno):
|
|
||||||
"""Prune breakpoints for filename:lineno.
|
|
||||||
|
|
||||||
A list of breakpoints is maintained in the Bdb instance and in
|
|
||||||
the Breakpoint class. If a breakpoint in the Bdb instance no
|
|
||||||
longer exists in the Breakpoint class, then it's removed from the
|
|
||||||
Bdb instance.
|
|
||||||
"""
|
|
||||||
if (filename, lineno) not in Breakpoint.bplist:
|
|
||||||
self.breaks[filename].remove(lineno)
|
|
||||||
if not self.breaks[filename]:
|
|
||||||
del self.breaks[filename]
|
|
||||||
|
|
||||||
def clear_break(self, filename, lineno):
|
|
||||||
"""Delete breakpoints for filename:lineno.
|
|
||||||
|
|
||||||
If no breakpoints were set, return an error message.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
if filename not in self.breaks:
|
|
||||||
return 'There are no breakpoints in %s' % filename
|
|
||||||
if lineno not in self.breaks[filename]:
|
|
||||||
return 'There is no breakpoint at %s:%d' % (filename, lineno)
|
|
||||||
# If there's only one bp in the list for that file,line
|
|
||||||
# pair, then remove the breaks entry
|
|
||||||
for bp in Breakpoint.bplist[filename, lineno][:]:
|
|
||||||
bp.deleteMe()
|
|
||||||
self._prune_breaks(filename, lineno)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def clear_bpbynumber(self, arg):
|
|
||||||
"""Delete a breakpoint by its index in Breakpoint.bpbynumber.
|
|
||||||
|
|
||||||
If arg is invalid, return an error message.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
bp = self.get_bpbynumber(arg)
|
|
||||||
except ValueError as err:
|
|
||||||
return str(err)
|
|
||||||
bp.deleteMe()
|
|
||||||
self._prune_breaks(bp.file, bp.line)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def clear_all_file_breaks(self, filename):
|
|
||||||
"""Delete all breakpoints in filename.
|
|
||||||
|
|
||||||
If none were set, return an error message.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
if filename not in self.breaks:
|
|
||||||
return 'There are no breakpoints in %s' % filename
|
|
||||||
for line in self.breaks[filename]:
|
|
||||||
blist = Breakpoint.bplist[filename, line]
|
|
||||||
for bp in blist:
|
|
||||||
bp.deleteMe()
|
|
||||||
del self.breaks[filename]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def clear_all_breaks(self):
|
|
||||||
"""Delete all existing breakpoints.
|
|
||||||
|
|
||||||
If none were set, return an error message.
|
|
||||||
"""
|
|
||||||
if not self.breaks:
|
|
||||||
return 'There are no breakpoints'
|
|
||||||
for bp in Breakpoint.bpbynumber:
|
|
||||||
if bp:
|
|
||||||
bp.deleteMe()
|
|
||||||
self.breaks = {}
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_bpbynumber(self, arg):
|
|
||||||
"""Return a breakpoint by its index in Breakpoint.bybpnumber.
|
|
||||||
|
|
||||||
For invalid arg values or if the breakpoint doesn't exist,
|
|
||||||
raise a ValueError.
|
|
||||||
"""
|
|
||||||
if not arg:
|
|
||||||
raise ValueError('Breakpoint number expected')
|
|
||||||
try:
|
|
||||||
number = int(arg)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError('Non-numeric breakpoint number %s' % arg) from None
|
|
||||||
try:
|
|
||||||
bp = Breakpoint.bpbynumber[number]
|
|
||||||
except IndexError:
|
|
||||||
raise ValueError('Breakpoint number %d out of range' % number) from None
|
|
||||||
if bp is None:
|
|
||||||
raise ValueError('Breakpoint %d already deleted' % number)
|
|
||||||
return bp
|
|
||||||
|
|
||||||
def get_break(self, filename, lineno):
|
|
||||||
"""Return True if there is a breakpoint for filename:lineno."""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
return filename in self.breaks and \
|
|
||||||
lineno in self.breaks[filename]
|
|
||||||
|
|
||||||
def get_breaks(self, filename, lineno):
|
|
||||||
"""Return all breakpoints for filename:lineno.
|
|
||||||
|
|
||||||
If no breakpoints are set, return an empty list.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
return filename in self.breaks and \
|
|
||||||
lineno in self.breaks[filename] and \
|
|
||||||
Breakpoint.bplist[filename, lineno] or []
|
|
||||||
|
|
||||||
def get_file_breaks(self, filename):
|
|
||||||
"""Return all lines with breakpoints for filename.
|
|
||||||
|
|
||||||
If no breakpoints are set, return an empty list.
|
|
||||||
"""
|
|
||||||
filename = self.canonic(filename)
|
|
||||||
if filename in self.breaks:
|
|
||||||
return self.breaks[filename]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_all_breaks(self):
|
|
||||||
"""Return all breakpoints that are set."""
|
|
||||||
return self.breaks
|
|
||||||
|
|
||||||
# Derived classes and clients can call the following method
|
|
||||||
# to get a data structure representing a stack trace.
|
|
||||||
|
|
||||||
def get_stack(self, f, t):
|
|
||||||
"""Return a list of (frame, lineno) in a stack trace and a size.
|
|
||||||
|
|
||||||
List starts with original calling frame, if there is one.
|
|
||||||
Size may be number of frames above or below f.
|
|
||||||
"""
|
|
||||||
stack = []
|
|
||||||
if t and t.tb_frame is f:
|
|
||||||
t = t.tb_next
|
|
||||||
while f is not None:
|
|
||||||
stack.append((f, f.f_lineno))
|
|
||||||
if f is self.botframe:
|
|
||||||
break
|
|
||||||
f = f.f_back
|
|
||||||
stack.reverse()
|
|
||||||
i = max(0, len(stack) - 1)
|
|
||||||
while t is not None:
|
|
||||||
stack.append((t.tb_frame, t.tb_lineno))
|
|
||||||
t = t.tb_next
|
|
||||||
if f is None:
|
|
||||||
i = max(0, len(stack) - 1)
|
|
||||||
return stack, i
|
|
||||||
|
|
||||||
def format_stack_entry(self, frame_lineno, lprefix=': '):
|
|
||||||
"""Return a string with information about a stack entry.
|
|
||||||
|
|
||||||
The stack entry frame_lineno is a (frame, lineno) tuple. The
|
|
||||||
return string contains the canonical filename, the function name
|
|
||||||
or '<lambda>', the input arguments, the return value, and the
|
|
||||||
line of code (if it exists).
|
|
||||||
|
|
||||||
"""
|
|
||||||
import linecache, reprlib
|
|
||||||
frame, lineno = frame_lineno
|
|
||||||
filename = self.canonic(frame.f_code.co_filename)
|
|
||||||
s = '%s(%r)' % (filename, lineno)
|
|
||||||
if frame.f_code.co_name:
|
|
||||||
s += frame.f_code.co_name
|
|
||||||
else:
|
|
||||||
s += "<lambda>"
|
|
||||||
s += '()'
|
|
||||||
if '__return__' in frame.f_locals:
|
|
||||||
rv = frame.f_locals['__return__']
|
|
||||||
s += '->'
|
|
||||||
s += reprlib.repr(rv)
|
|
||||||
line = linecache.getline(filename, lineno, frame.f_globals)
|
|
||||||
if line:
|
|
||||||
s += lprefix + line.strip()
|
|
||||||
return s
|
|
||||||
|
|
||||||
# The following methods can be called by clients to use
|
|
||||||
# a debugger to debug a statement or an expression.
|
|
||||||
# Both can be given as a string, or a code object.
|
|
||||||
|
|
||||||
def run(self, cmd, globals=None, locals=None):
|
|
||||||
"""Debug a statement executed via the exec() function.
|
|
||||||
|
|
||||||
globals defaults to __main__.dict; locals defaults to globals.
|
|
||||||
"""
|
|
||||||
if globals is None:
|
|
||||||
import __main__
|
|
||||||
globals = __main__.__dict__
|
|
||||||
if locals is None:
|
|
||||||
locals = globals
|
|
||||||
self.reset()
|
|
||||||
if isinstance(cmd, str):
|
|
||||||
cmd = compile(cmd, "<string>", "exec")
|
|
||||||
sys.settrace(self.trace_dispatch)
|
|
||||||
try:
|
|
||||||
exec(cmd, globals, locals)
|
|
||||||
except BdbQuit:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.quitting = True
|
|
||||||
sys.settrace(None)
|
|
||||||
|
|
||||||
def runeval(self, expr, globals=None, locals=None):
|
|
||||||
"""Debug an expression executed via the eval() function.
|
|
||||||
|
|
||||||
globals defaults to __main__.dict; locals defaults to globals.
|
|
||||||
"""
|
|
||||||
if globals is None:
|
|
||||||
import __main__
|
|
||||||
globals = __main__.__dict__
|
|
||||||
if locals is None:
|
|
||||||
locals = globals
|
|
||||||
self.reset()
|
|
||||||
sys.settrace(self.trace_dispatch)
|
|
||||||
try:
|
|
||||||
return eval(expr, globals, locals)
|
|
||||||
except BdbQuit:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.quitting = True
|
|
||||||
sys.settrace(None)
|
|
||||||
|
|
||||||
def runctx(self, cmd, globals, locals):
|
|
||||||
"""For backwards-compatibility. Defers to run()."""
|
|
||||||
# B/W compatibility
|
|
||||||
self.run(cmd, globals, locals)
|
|
||||||
|
|
||||||
# This method is more useful to debug a single function call.
|
|
||||||
|
|
||||||
def runcall(*args, **kwds):
|
|
||||||
"""Debug a single function call.
|
|
||||||
|
|
||||||
Return the result of the function call.
|
|
||||||
"""
|
|
||||||
if len(args) >= 2:
|
|
||||||
self, func, *args = args
|
|
||||||
elif not args:
|
|
||||||
raise TypeError("descriptor 'runcall' of 'Bdb' object "
|
|
||||||
"needs an argument")
|
|
||||||
elif 'func' in kwds:
|
|
||||||
func = kwds.pop('func')
|
|
||||||
self, *args = args
|
|
||||||
import warnings
|
|
||||||
warnings.warn("Passing 'func' as keyword argument is deprecated",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
else:
|
|
||||||
raise TypeError('runcall expected at least 1 positional argument, '
|
|
||||||
'got %d' % (len(args)-1))
|
|
||||||
|
|
||||||
self.reset()
|
|
||||||
sys.settrace(self.trace_dispatch)
|
|
||||||
res = None
|
|
||||||
try:
|
|
||||||
res = func(*args, **kwds)
|
|
||||||
except BdbQuit:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.quitting = True
|
|
||||||
sys.settrace(None)
|
|
||||||
return res
|
|
||||||
runcall.__text_signature__ = '($self, func, /, *args, **kwds)'
|
|
||||||
|
|
||||||
|
|
||||||
def set_trace():
|
|
||||||
"""Start debugging with a Bdb instance from the caller's frame."""
|
|
||||||
Bdb().set_trace()
|
|
||||||
|
|
||||||
|
|
||||||
class Breakpoint:
|
|
||||||
"""Breakpoint class.
|
|
||||||
|
|
||||||
Implements temporary breakpoints, ignore counts, disabling and
|
|
||||||
(re)-enabling, and conditionals.
|
|
||||||
|
|
||||||
Breakpoints are indexed by number through bpbynumber and by
|
|
||||||
the (file, line) tuple using bplist. The former points to a
|
|
||||||
single instance of class Breakpoint. The latter points to a
|
|
||||||
list of such instances since there may be more than one
|
|
||||||
breakpoint per line.
|
|
||||||
|
|
||||||
When creating a breakpoint, its associated filename should be
|
|
||||||
in canonical form. If funcname is defined, a breakpoint hit will be
|
|
||||||
counted when the first line of that function is executed. A
|
|
||||||
conditional breakpoint always counts a hit.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# XXX Keeping state in the class is a mistake -- this means
|
|
||||||
# you cannot have more than one active Bdb instance.
|
|
||||||
|
|
||||||
next = 1 # Next bp to be assigned
|
|
||||||
bplist = {} # indexed by (file, lineno) tuple
|
|
||||||
bpbynumber = [None] # Each entry is None or an instance of Bpt
|
|
||||||
# index 0 is unused, except for marking an
|
|
||||||
# effective break .... see effective()
|
|
||||||
|
|
||||||
def __init__(self, file, line, temporary=False, cond=None, funcname=None):
|
|
||||||
self.funcname = funcname
|
|
||||||
# Needed if funcname is not None.
|
|
||||||
self.func_first_executable_line = None
|
|
||||||
self.file = file # This better be in canonical form!
|
|
||||||
self.line = line
|
|
||||||
self.temporary = temporary
|
|
||||||
self.cond = cond
|
|
||||||
self.enabled = True
|
|
||||||
self.ignore = 0
|
|
||||||
self.hits = 0
|
|
||||||
self.number = Breakpoint.next
|
|
||||||
Breakpoint.next += 1
|
|
||||||
# Build the two lists
|
|
||||||
self.bpbynumber.append(self)
|
|
||||||
if (file, line) in self.bplist:
|
|
||||||
self.bplist[file, line].append(self)
|
|
||||||
else:
|
|
||||||
self.bplist[file, line] = [self]
|
|
||||||
|
|
||||||
def deleteMe(self):
|
|
||||||
"""Delete the breakpoint from the list associated to a file:line.
|
|
||||||
|
|
||||||
If it is the last breakpoint in that position, it also deletes
|
|
||||||
the entry for the file:line.
|
|
||||||
"""
|
|
||||||
|
|
||||||
index = (self.file, self.line)
|
|
||||||
self.bpbynumber[self.number] = None # No longer in list
|
|
||||||
self.bplist[index].remove(self)
|
|
||||||
if not self.bplist[index]:
|
|
||||||
# No more bp for this f:l combo
|
|
||||||
del self.bplist[index]
|
|
||||||
|
|
||||||
def enable(self):
|
|
||||||
"""Mark the breakpoint as enabled."""
|
|
||||||
self.enabled = True
|
|
||||||
|
|
||||||
def disable(self):
|
|
||||||
"""Mark the breakpoint as disabled."""
|
|
||||||
self.enabled = False
|
|
||||||
|
|
||||||
def bpprint(self, out=None):
|
|
||||||
"""Print the output of bpformat().
|
|
||||||
|
|
||||||
The optional out argument directs where the output is sent
|
|
||||||
and defaults to standard output.
|
|
||||||
"""
|
|
||||||
if out is None:
|
|
||||||
out = sys.stdout
|
|
||||||
print(self.bpformat(), file=out)
|
|
||||||
|
|
||||||
def bpformat(self):
|
|
||||||
"""Return a string with information about the breakpoint.
|
|
||||||
|
|
||||||
The information includes the breakpoint number, temporary
|
|
||||||
status, file:line position, break condition, number of times to
|
|
||||||
ignore, and number of times hit.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.temporary:
|
|
||||||
disp = 'del '
|
|
||||||
else:
|
|
||||||
disp = 'keep '
|
|
||||||
if self.enabled:
|
|
||||||
disp = disp + 'yes '
|
|
||||||
else:
|
|
||||||
disp = disp + 'no '
|
|
||||||
ret = '%-4dbreakpoint %s at %s:%d' % (self.number, disp,
|
|
||||||
self.file, self.line)
|
|
||||||
if self.cond:
|
|
||||||
ret += '\n\tstop only if %s' % (self.cond,)
|
|
||||||
if self.ignore:
|
|
||||||
ret += '\n\tignore next %d hits' % (self.ignore,)
|
|
||||||
if self.hits:
|
|
||||||
if self.hits > 1:
|
|
||||||
ss = 's'
|
|
||||||
else:
|
|
||||||
ss = ''
|
|
||||||
ret += '\n\tbreakpoint already hit %d time%s' % (self.hits, ss)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"Return a condensed description of the breakpoint."
|
|
||||||
return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
|
|
||||||
|
|
||||||
# -----------end of Breakpoint class----------
|
|
||||||
|
|
||||||
|
|
||||||
def checkfuncname(b, frame):
|
|
||||||
"""Return True if break should happen here.
|
|
||||||
|
|
||||||
Whether a break should happen depends on the way that b (the breakpoint)
|
|
||||||
was set. If it was set via line number, check if b.line is the same as
|
|
||||||
the one in the frame. If it was set via function name, check if this is
|
|
||||||
the right function and if it is on the first executable line.
|
|
||||||
"""
|
|
||||||
if not b.funcname:
|
|
||||||
# Breakpoint was set via line number.
|
|
||||||
if b.line != frame.f_lineno:
|
|
||||||
# Breakpoint was set at a line with a def statement and the function
|
|
||||||
# defined is called: don't break.
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Breakpoint set via function name.
|
|
||||||
if frame.f_code.co_name != b.funcname:
|
|
||||||
# It's not a function call, but rather execution of def statement.
|
|
||||||
return False
|
|
||||||
|
|
||||||
# We are in the right frame.
|
|
||||||
if not b.func_first_executable_line:
|
|
||||||
# The function is entered for the 1st time.
|
|
||||||
b.func_first_executable_line = frame.f_lineno
|
|
||||||
|
|
||||||
if b.func_first_executable_line != frame.f_lineno:
|
|
||||||
# But we are not at the first line number: don't break.
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# Determines if there is an effective (active) breakpoint at this
|
|
||||||
# line of code. Returns breakpoint number or 0 if none
|
|
||||||
def effective(file, line, frame):
|
|
||||||
"""Determine which breakpoint for this file:line is to be acted upon.
|
|
||||||
|
|
||||||
Called only if we know there is a breakpoint at this location. Return
|
|
||||||
the breakpoint that was triggered and a boolean that indicates if it is
|
|
||||||
ok to delete a temporary breakpoint. Return (None, None) if there is no
|
|
||||||
matching breakpoint.
|
|
||||||
"""
|
|
||||||
possibles = Breakpoint.bplist[file, line]
|
|
||||||
for b in possibles:
|
|
||||||
if not b.enabled:
|
|
||||||
continue
|
|
||||||
if not checkfuncname(b, frame):
|
|
||||||
continue
|
|
||||||
# Count every hit when bp is enabled
|
|
||||||
b.hits += 1
|
|
||||||
if not b.cond:
|
|
||||||
# If unconditional, and ignoring go on to next, else break
|
|
||||||
if b.ignore > 0:
|
|
||||||
b.ignore -= 1
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# breakpoint and marker that it's ok to delete if temporary
|
|
||||||
return (b, True)
|
|
||||||
else:
|
|
||||||
# Conditional bp.
|
|
||||||
# Ignore count applies only to those bpt hits where the
|
|
||||||
# condition evaluates to true.
|
|
||||||
try:
|
|
||||||
val = eval(b.cond, frame.f_globals, frame.f_locals)
|
|
||||||
if val:
|
|
||||||
if b.ignore > 0:
|
|
||||||
b.ignore -= 1
|
|
||||||
# continue
|
|
||||||
else:
|
|
||||||
return (b, True)
|
|
||||||
# else:
|
|
||||||
# continue
|
|
||||||
except:
|
|
||||||
# if eval fails, most conservative thing is to stop on
|
|
||||||
# breakpoint regardless of ignore count. Don't delete
|
|
||||||
# temporary, as another hint to user.
|
|
||||||
return (b, False)
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------- testing --------------------
|
|
||||||
|
|
||||||
class Tdb(Bdb):
|
|
||||||
def user_call(self, frame, args):
|
|
||||||
name = frame.f_code.co_name
|
|
||||||
if not name: name = '???'
|
|
||||||
print('+++ call', name, args)
|
|
||||||
def user_line(self, frame):
|
|
||||||
import linecache
|
|
||||||
name = frame.f_code.co_name
|
|
||||||
if not name: name = '???'
|
|
||||||
fn = self.canonic(frame.f_code.co_filename)
|
|
||||||
line = linecache.getline(fn, frame.f_lineno, frame.f_globals)
|
|
||||||
print('+++', fn, frame.f_lineno, name, ':', line.strip())
|
|
||||||
def user_return(self, frame, retval):
|
|
||||||
print('+++ return', retval)
|
|
||||||
def user_exception(self, frame, exc_stuff):
|
|
||||||
print('+++ exception', exc_stuff)
|
|
||||||
self.set_continue()
|
|
||||||
|
|
||||||
def foo(n):
|
|
||||||
print('foo(', n, ')')
|
|
||||||
x = bar(n*10)
|
|
||||||
print('bar returned', x)
|
|
||||||
|
|
||||||
def bar(a):
|
|
||||||
print('bar(', a, ')')
|
|
||||||
return a/2
|
|
||||||
|
|
||||||
def test():
|
|
||||||
t = Tdb()
|
|
||||||
t.run('import bdb; bdb.foo(10)')
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
"""Bisection algorithms."""
|
|
||||||
|
|
||||||
def insort_right(a, x, lo=0, hi=None):
|
|
||||||
"""Insert item x in list a, and keep it sorted assuming a is sorted.
|
|
||||||
|
|
||||||
If x is already in a, insert it to the right of the rightmost x.
|
|
||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
||||||
slice of a to be searched.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if lo < 0:
|
|
||||||
raise ValueError('lo must be non-negative')
|
|
||||||
if hi is None:
|
|
||||||
hi = len(a)
|
|
||||||
while lo < hi:
|
|
||||||
mid = (lo+hi)//2
|
|
||||||
if x < a[mid]: hi = mid
|
|
||||||
else: lo = mid+1
|
|
||||||
a.insert(lo, x)
|
|
||||||
|
|
||||||
insort = insort_right # backward compatibility
|
|
||||||
|
|
||||||
def bisect_right(a, x, lo=0, hi=None):
|
|
||||||
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
||||||
|
|
||||||
The return value i is such that all e in a[:i] have e <= x, and all e in
|
|
||||||
a[i:] have e > x. So if x already appears in the list, a.insert(x) will
|
|
||||||
insert just after the rightmost x already there.
|
|
||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
||||||
slice of a to be searched.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if lo < 0:
|
|
||||||
raise ValueError('lo must be non-negative')
|
|
||||||
if hi is None:
|
|
||||||
hi = len(a)
|
|
||||||
while lo < hi:
|
|
||||||
mid = (lo+hi)//2
|
|
||||||
if x < a[mid]: hi = mid
|
|
||||||
else: lo = mid+1
|
|
||||||
return lo
|
|
||||||
|
|
||||||
bisect = bisect_right # backward compatibility
|
|
||||||
|
|
||||||
def insort_left(a, x, lo=0, hi=None):
|
|
||||||
"""Insert item x in list a, and keep it sorted assuming a is sorted.
|
|
||||||
|
|
||||||
If x is already in a, insert it to the left of the leftmost x.
|
|
||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
||||||
slice of a to be searched.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if lo < 0:
|
|
||||||
raise ValueError('lo must be non-negative')
|
|
||||||
if hi is None:
|
|
||||||
hi = len(a)
|
|
||||||
while lo < hi:
|
|
||||||
mid = (lo+hi)//2
|
|
||||||
if a[mid] < x: lo = mid+1
|
|
||||||
else: hi = mid
|
|
||||||
a.insert(lo, x)
|
|
||||||
|
|
||||||
|
|
||||||
def bisect_left(a, x, lo=0, hi=None):
|
|
||||||
"""Return the index where to insert item x in list a, assuming a is sorted.
|
|
||||||
|
|
||||||
The return value i is such that all e in a[:i] have e < x, and all e in
|
|
||||||
a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
|
|
||||||
insert just before the leftmost x already there.
|
|
||||||
|
|
||||||
Optional args lo (default 0) and hi (default len(a)) bound the
|
|
||||||
slice of a to be searched.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if lo < 0:
|
|
||||||
raise ValueError('lo must be non-negative')
|
|
||||||
if hi is None:
|
|
||||||
hi = len(a)
|
|
||||||
while lo < hi:
|
|
||||||
mid = (lo+hi)//2
|
|
||||||
if a[mid] < x: lo = mid+1
|
|
||||||
else: hi = mid
|
|
||||||
return lo
|
|
||||||
|
|
||||||
# Overwrite above definitions with a fast C implementation
|
|
||||||
try:
|
|
||||||
from _bisect import *
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
713
Lib/calendar.py
713
Lib/calendar.py
@@ -1,713 +0,0 @@
|
|||||||
"""Calendar printing functions
|
|
||||||
|
|
||||||
Note when comparing these calendars to the ones printed by cal(1): By
|
|
||||||
default, these calendars have Monday as the first day of the week, and
|
|
||||||
Sunday as the last (the European convention). Use setfirstweekday() to
|
|
||||||
set the first day of the week (0=Monday, 6=Sunday)."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
import locale as _locale
|
|
||||||
from itertools import repeat
|
|
||||||
|
|
||||||
__all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday",
|
|
||||||
"firstweekday", "isleap", "leapdays", "weekday", "monthrange",
|
|
||||||
"monthcalendar", "prmonth", "month", "prcal", "calendar",
|
|
||||||
"timegm", "month_name", "month_abbr", "day_name", "day_abbr",
|
|
||||||
"Calendar", "TextCalendar", "HTMLCalendar", "LocaleTextCalendar",
|
|
||||||
"LocaleHTMLCalendar", "weekheader"]
|
|
||||||
|
|
||||||
# Exception raised for bad input (with string parameter for details)
|
|
||||||
error = ValueError
|
|
||||||
|
|
||||||
# Exceptions raised for bad input
|
|
||||||
class IllegalMonthError(ValueError):
|
|
||||||
def __init__(self, month):
|
|
||||||
self.month = month
|
|
||||||
def __str__(self):
|
|
||||||
return "bad month number %r; must be 1-12" % self.month
|
|
||||||
|
|
||||||
|
|
||||||
class IllegalWeekdayError(ValueError):
|
|
||||||
def __init__(self, weekday):
|
|
||||||
self.weekday = weekday
|
|
||||||
def __str__(self):
|
|
||||||
return "bad weekday number %r; must be 0 (Monday) to 6 (Sunday)" % self.weekday
|
|
||||||
|
|
||||||
|
|
||||||
# Constants for months referenced later
|
|
||||||
January = 1
|
|
||||||
February = 2
|
|
||||||
|
|
||||||
# Number of days per month (except for February in leap years)
|
|
||||||
mdays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
||||||
|
|
||||||
# This module used to have hard-coded lists of day and month names, as
|
|
||||||
# English strings. The classes following emulate a read-only version of
|
|
||||||
# that, but supply localized names. Note that the values are computed
|
|
||||||
# fresh on each call, in case the user changes locale between calls.
|
|
||||||
|
|
||||||
class _localized_month:
|
|
||||||
|
|
||||||
_months = [datetime.date(2001, i+1, 1).strftime for i in range(12)]
|
|
||||||
_months.insert(0, lambda x: "")
|
|
||||||
|
|
||||||
def __init__(self, format):
|
|
||||||
self.format = format
|
|
||||||
|
|
||||||
def __getitem__(self, i):
|
|
||||||
funcs = self._months[i]
|
|
||||||
if isinstance(i, slice):
|
|
||||||
return [f(self.format) for f in funcs]
|
|
||||||
else:
|
|
||||||
return funcs(self.format)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return 13
|
|
||||||
|
|
||||||
|
|
||||||
class _localized_day:
|
|
||||||
|
|
||||||
# January 1, 2001, was a Monday.
|
|
||||||
_days = [datetime.date(2001, 1, i+1).strftime for i in range(7)]
|
|
||||||
|
|
||||||
def __init__(self, format):
|
|
||||||
self.format = format
|
|
||||||
|
|
||||||
def __getitem__(self, i):
|
|
||||||
funcs = self._days[i]
|
|
||||||
if isinstance(i, slice):
|
|
||||||
return [f(self.format) for f in funcs]
|
|
||||||
else:
|
|
||||||
return funcs(self.format)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return 7
|
|
||||||
|
|
||||||
|
|
||||||
# Full and abbreviated names of weekdays
|
|
||||||
day_name = _localized_day('%A')
|
|
||||||
day_abbr = _localized_day('%a')
|
|
||||||
|
|
||||||
# Full and abbreviated names of months (1-based arrays!!!)
|
|
||||||
month_name = _localized_month('%B')
|
|
||||||
month_abbr = _localized_month('%b')
|
|
||||||
|
|
||||||
# Constants for weekdays
|
|
||||||
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
|
|
||||||
|
|
||||||
|
|
||||||
def isleap(year):
|
|
||||||
"""Return True for leap years, False for non-leap years."""
|
|
||||||
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
|
||||||
|
|
||||||
|
|
||||||
def leapdays(y1, y2):
|
|
||||||
"""Return number of leap years in range [y1, y2).
|
|
||||||
Assume y1 <= y2."""
|
|
||||||
y1 -= 1
|
|
||||||
y2 -= 1
|
|
||||||
return (y2//4 - y1//4) - (y2//100 - y1//100) + (y2//400 - y1//400)
|
|
||||||
|
|
||||||
|
|
||||||
def weekday(year, month, day):
|
|
||||||
"""Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12),
|
|
||||||
day (1-31)."""
|
|
||||||
return datetime.date(year, month, day).weekday()
|
|
||||||
|
|
||||||
|
|
||||||
def monthrange(year, month):
|
|
||||||
"""Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for
|
|
||||||
year, month."""
|
|
||||||
if not 1 <= month <= 12:
|
|
||||||
raise IllegalMonthError(month)
|
|
||||||
day1 = weekday(year, month, 1)
|
|
||||||
ndays = mdays[month] + (month == February and isleap(year))
|
|
||||||
return day1, ndays
|
|
||||||
|
|
||||||
|
|
||||||
class Calendar(object):
|
|
||||||
"""
|
|
||||||
Base calendar class. This class doesn't do any formatting. It simply
|
|
||||||
provides data to subclasses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, firstweekday=0):
|
|
||||||
self.firstweekday = firstweekday # 0 = Monday, 6 = Sunday
|
|
||||||
|
|
||||||
def getfirstweekday(self):
|
|
||||||
return self._firstweekday % 7
|
|
||||||
|
|
||||||
def setfirstweekday(self, firstweekday):
|
|
||||||
self._firstweekday = firstweekday
|
|
||||||
|
|
||||||
firstweekday = property(getfirstweekday, setfirstweekday)
|
|
||||||
|
|
||||||
def iterweekdays(self):
|
|
||||||
"""
|
|
||||||
Return an iterator for one week of weekday numbers starting with the
|
|
||||||
configured first one.
|
|
||||||
"""
|
|
||||||
for i in range(self.firstweekday, self.firstweekday + 7):
|
|
||||||
yield i%7
|
|
||||||
|
|
||||||
def itermonthdates(self, year, month):
|
|
||||||
"""
|
|
||||||
Return an iterator for one month. The iterator will yield datetime.date
|
|
||||||
values and will always iterate through complete weeks, so it will yield
|
|
||||||
dates outside the specified month.
|
|
||||||
"""
|
|
||||||
date = datetime.date(year, month, 1)
|
|
||||||
# Go back to the beginning of the week
|
|
||||||
days = (date.weekday() - self.firstweekday) % 7
|
|
||||||
date -= datetime.timedelta(days=days)
|
|
||||||
oneday = datetime.timedelta(days=1)
|
|
||||||
while True:
|
|
||||||
yield date
|
|
||||||
try:
|
|
||||||
date += oneday
|
|
||||||
except OverflowError:
|
|
||||||
# Adding one day could fail after datetime.MAXYEAR
|
|
||||||
break
|
|
||||||
if date.month != month and date.weekday() == self.firstweekday:
|
|
||||||
break
|
|
||||||
|
|
||||||
def itermonthdays2(self, year, month):
|
|
||||||
"""
|
|
||||||
Like itermonthdates(), but will yield (day number, weekday number)
|
|
||||||
tuples. For days outside the specified month the day number is 0.
|
|
||||||
"""
|
|
||||||
for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
|
|
||||||
yield d, i % 7
|
|
||||||
|
|
||||||
def itermonthdays(self, year, month):
|
|
||||||
"""
|
|
||||||
Like itermonthdates(), but will yield day numbers. For days outside
|
|
||||||
the specified month the day number is 0.
|
|
||||||
"""
|
|
||||||
day1, ndays = monthrange(year, month)
|
|
||||||
days_before = (day1 - self.firstweekday) % 7
|
|
||||||
yield from repeat(0, days_before)
|
|
||||||
yield from range(1, ndays + 1)
|
|
||||||
days_after = (self.firstweekday - day1 - ndays) % 7
|
|
||||||
yield from repeat(0, days_after)
|
|
||||||
|
|
||||||
def monthdatescalendar(self, year, month):
|
|
||||||
"""
|
|
||||||
Return a matrix (list of lists) representing a month's calendar.
|
|
||||||
Each row represents a week; week entries are datetime.date values.
|
|
||||||
"""
|
|
||||||
dates = list(self.itermonthdates(year, month))
|
|
||||||
return [ dates[i:i+7] for i in range(0, len(dates), 7) ]
|
|
||||||
|
|
||||||
def monthdays2calendar(self, year, month):
|
|
||||||
"""
|
|
||||||
Return a matrix representing a month's calendar.
|
|
||||||
Each row represents a week; week entries are
|
|
||||||
(day number, weekday number) tuples. Day numbers outside this month
|
|
||||||
are zero.
|
|
||||||
"""
|
|
||||||
days = list(self.itermonthdays2(year, month))
|
|
||||||
return [ days[i:i+7] for i in range(0, len(days), 7) ]
|
|
||||||
|
|
||||||
def monthdayscalendar(self, year, month):
|
|
||||||
"""
|
|
||||||
Return a matrix representing a month's calendar.
|
|
||||||
Each row represents a week; days outside this month are zero.
|
|
||||||
"""
|
|
||||||
days = list(self.itermonthdays(year, month))
|
|
||||||
return [ days[i:i+7] for i in range(0, len(days), 7) ]
|
|
||||||
|
|
||||||
def yeardatescalendar(self, year, width=3):
|
|
||||||
"""
|
|
||||||
Return the data for the specified year ready for formatting. The return
|
|
||||||
value is a list of month rows. Each month row contains up to width months.
|
|
||||||
Each month contains between 4 and 6 weeks and each week contains 1-7
|
|
||||||
days. Days are datetime.date objects.
|
|
||||||
"""
|
|
||||||
months = [
|
|
||||||
self.monthdatescalendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
|
||||||
|
|
||||||
def yeardays2calendar(self, year, width=3):
|
|
||||||
"""
|
|
||||||
Return the data for the specified year ready for formatting (similar to
|
|
||||||
yeardatescalendar()). Entries in the week lists are
|
|
||||||
(day number, weekday number) tuples. Day numbers outside this month are
|
|
||||||
zero.
|
|
||||||
"""
|
|
||||||
months = [
|
|
||||||
self.monthdays2calendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
|
||||||
|
|
||||||
def yeardayscalendar(self, year, width=3):
|
|
||||||
"""
|
|
||||||
Return the data for the specified year ready for formatting (similar to
|
|
||||||
yeardatescalendar()). Entries in the week lists are day numbers.
|
|
||||||
Day numbers outside this month are zero.
|
|
||||||
"""
|
|
||||||
months = [
|
|
||||||
self.monthdayscalendar(year, i)
|
|
||||||
for i in range(January, January+12)
|
|
||||||
]
|
|
||||||
return [months[i:i+width] for i in range(0, len(months), width) ]
|
|
||||||
|
|
||||||
|
|
||||||
class TextCalendar(Calendar):
|
|
||||||
"""
|
|
||||||
Subclass of Calendar that outputs a calendar as a simple plain text
|
|
||||||
similar to the UNIX program cal.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def prweek(self, theweek, width):
|
|
||||||
"""
|
|
||||||
Print a single week (no newline).
|
|
||||||
"""
|
|
||||||
print(self.formatweek(theweek, width), end=' ')
|
|
||||||
|
|
||||||
def formatday(self, day, weekday, width):
|
|
||||||
"""
|
|
||||||
Returns a formatted day.
|
|
||||||
"""
|
|
||||||
if day == 0:
|
|
||||||
s = ''
|
|
||||||
else:
|
|
||||||
s = '%2i' % day # right-align single-digit days
|
|
||||||
return s.center(width)
|
|
||||||
|
|
||||||
def formatweek(self, theweek, width):
|
|
||||||
"""
|
|
||||||
Returns a single week in a string (no newline).
|
|
||||||
"""
|
|
||||||
return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
|
|
||||||
|
|
||||||
def formatweekday(self, day, width):
|
|
||||||
"""
|
|
||||||
Returns a formatted week day name.
|
|
||||||
"""
|
|
||||||
if width >= 9:
|
|
||||||
names = day_name
|
|
||||||
else:
|
|
||||||
names = day_abbr
|
|
||||||
return names[day][:width].center(width)
|
|
||||||
|
|
||||||
def formatweekheader(self, width):
|
|
||||||
"""
|
|
||||||
Return a header for a week.
|
|
||||||
"""
|
|
||||||
return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, width, withyear=True):
|
|
||||||
"""
|
|
||||||
Return a formatted month name.
|
|
||||||
"""
|
|
||||||
s = month_name[themonth]
|
|
||||||
if withyear:
|
|
||||||
s = "%s %r" % (s, theyear)
|
|
||||||
return s.center(width)
|
|
||||||
|
|
||||||
def prmonth(self, theyear, themonth, w=0, l=0):
|
|
||||||
"""
|
|
||||||
Print a month's calendar.
|
|
||||||
"""
|
|
||||||
print(self.formatmonth(theyear, themonth, w, l), end='')
|
|
||||||
|
|
||||||
def formatmonth(self, theyear, themonth, w=0, l=0):
|
|
||||||
"""
|
|
||||||
Return a month's calendar string (multi-line).
|
|
||||||
"""
|
|
||||||
w = max(2, w)
|
|
||||||
l = max(1, l)
|
|
||||||
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
|
|
||||||
s = s.rstrip()
|
|
||||||
s += '\n' * l
|
|
||||||
s += self.formatweekheader(w).rstrip()
|
|
||||||
s += '\n' * l
|
|
||||||
for week in self.monthdays2calendar(theyear, themonth):
|
|
||||||
s += self.formatweek(week, w).rstrip()
|
|
||||||
s += '\n' * l
|
|
||||||
return s
|
|
||||||
|
|
||||||
def formatyear(self, theyear, w=2, l=1, c=6, m=3):
|
|
||||||
"""
|
|
||||||
Returns a year's calendar as a multi-line string.
|
|
||||||
"""
|
|
||||||
w = max(2, w)
|
|
||||||
l = max(1, l)
|
|
||||||
c = max(2, c)
|
|
||||||
colwidth = (w + 1) * 7 - 1
|
|
||||||
v = []
|
|
||||||
a = v.append
|
|
||||||
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
|
|
||||||
a('\n'*l)
|
|
||||||
header = self.formatweekheader(w)
|
|
||||||
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
|
|
||||||
# months in this row
|
|
||||||
months = range(m*i+1, min(m*(i+1)+1, 13))
|
|
||||||
a('\n'*l)
|
|
||||||
names = (self.formatmonthname(theyear, k, colwidth, False)
|
|
||||||
for k in months)
|
|
||||||
a(formatstring(names, colwidth, c).rstrip())
|
|
||||||
a('\n'*l)
|
|
||||||
headers = (header for k in months)
|
|
||||||
a(formatstring(headers, colwidth, c).rstrip())
|
|
||||||
a('\n'*l)
|
|
||||||
# max number of weeks for this row
|
|
||||||
height = max(len(cal) for cal in row)
|
|
||||||
for j in range(height):
|
|
||||||
weeks = []
|
|
||||||
for cal in row:
|
|
||||||
if j >= len(cal):
|
|
||||||
weeks.append('')
|
|
||||||
else:
|
|
||||||
weeks.append(self.formatweek(cal[j], w))
|
|
||||||
a(formatstring(weeks, colwidth, c).rstrip())
|
|
||||||
a('\n' * l)
|
|
||||||
return ''.join(v)
|
|
||||||
|
|
||||||
def pryear(self, theyear, w=0, l=0, c=6, m=3):
|
|
||||||
"""Print a year's calendar."""
|
|
||||||
print(self.formatyear(theyear, w, l, c, m))
|
|
||||||
|
|
||||||
|
|
||||||
class HTMLCalendar(Calendar):
|
|
||||||
"""
|
|
||||||
This calendar returns complete HTML pages.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# CSS classes for the day <td>s
|
|
||||||
cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
|
||||||
|
|
||||||
def formatday(self, day, weekday):
|
|
||||||
"""
|
|
||||||
Return a day as a table cell.
|
|
||||||
"""
|
|
||||||
if day == 0:
|
|
||||||
return '<td class="noday"> </td>' # day outside month
|
|
||||||
else:
|
|
||||||
return '<td class="%s">%d</td>' % (self.cssclasses[weekday], day)
|
|
||||||
|
|
||||||
def formatweek(self, theweek):
|
|
||||||
"""
|
|
||||||
Return a complete week as a table row.
|
|
||||||
"""
|
|
||||||
s = ''.join(self.formatday(d, wd) for (d, wd) in theweek)
|
|
||||||
return '<tr>%s</tr>' % s
|
|
||||||
|
|
||||||
def formatweekday(self, day):
|
|
||||||
"""
|
|
||||||
Return a weekday name as a table header.
|
|
||||||
"""
|
|
||||||
return '<th class="%s">%s</th>' % (self.cssclasses[day], day_abbr[day])
|
|
||||||
|
|
||||||
def formatweekheader(self):
|
|
||||||
"""
|
|
||||||
Return a header for a week as a table row.
|
|
||||||
"""
|
|
||||||
s = ''.join(self.formatweekday(i) for i in self.iterweekdays())
|
|
||||||
return '<tr>%s</tr>' % s
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, withyear=True):
|
|
||||||
"""
|
|
||||||
Return a month name as a table row.
|
|
||||||
"""
|
|
||||||
if withyear:
|
|
||||||
s = '%s %s' % (month_name[themonth], theyear)
|
|
||||||
else:
|
|
||||||
s = '%s' % month_name[themonth]
|
|
||||||
return '<tr><th colspan="7" class="month">%s</th></tr>' % s
|
|
||||||
|
|
||||||
def formatmonth(self, theyear, themonth, withyear=True):
|
|
||||||
"""
|
|
||||||
Return a formatted month as a table.
|
|
||||||
"""
|
|
||||||
v = []
|
|
||||||
a = v.append
|
|
||||||
a('<table border="0" cellpadding="0" cellspacing="0" class="month">')
|
|
||||||
a('\n')
|
|
||||||
a(self.formatmonthname(theyear, themonth, withyear=withyear))
|
|
||||||
a('\n')
|
|
||||||
a(self.formatweekheader())
|
|
||||||
a('\n')
|
|
||||||
for week in self.monthdays2calendar(theyear, themonth):
|
|
||||||
a(self.formatweek(week))
|
|
||||||
a('\n')
|
|
||||||
a('</table>')
|
|
||||||
a('\n')
|
|
||||||
return ''.join(v)
|
|
||||||
|
|
||||||
def formatyear(self, theyear, width=3):
|
|
||||||
"""
|
|
||||||
Return a formatted year as a table of tables.
|
|
||||||
"""
|
|
||||||
v = []
|
|
||||||
a = v.append
|
|
||||||
width = max(width, 1)
|
|
||||||
a('<table border="0" cellpadding="0" cellspacing="0" class="year">')
|
|
||||||
a('\n')
|
|
||||||
a('<tr><th colspan="%d" class="year">%s</th></tr>' % (width, theyear))
|
|
||||||
for i in range(January, January+12, width):
|
|
||||||
# months in this row
|
|
||||||
months = range(i, min(i+width, 13))
|
|
||||||
a('<tr>')
|
|
||||||
for m in months:
|
|
||||||
a('<td>')
|
|
||||||
a(self.formatmonth(theyear, m, withyear=False))
|
|
||||||
a('</td>')
|
|
||||||
a('</tr>')
|
|
||||||
a('</table>')
|
|
||||||
return ''.join(v)
|
|
||||||
|
|
||||||
def formatyearpage(self, theyear, width=3, css='calendar.css', encoding=None):
|
|
||||||
"""
|
|
||||||
Return a formatted year as a complete HTML page.
|
|
||||||
"""
|
|
||||||
if encoding is None:
|
|
||||||
encoding = sys.getdefaultencoding()
|
|
||||||
v = []
|
|
||||||
a = v.append
|
|
||||||
a('<?xml version="1.0" encoding="%s"?>\n' % encoding)
|
|
||||||
a('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n')
|
|
||||||
a('<html>\n')
|
|
||||||
a('<head>\n')
|
|
||||||
a('<meta http-equiv="Content-Type" content="text/html; charset=%s" />\n' % encoding)
|
|
||||||
if css is not None:
|
|
||||||
a('<link rel="stylesheet" type="text/css" href="%s" />\n' % css)
|
|
||||||
a('<title>Calendar for %d</title>\n' % theyear)
|
|
||||||
a('</head>\n')
|
|
||||||
a('<body>\n')
|
|
||||||
a(self.formatyear(theyear, width))
|
|
||||||
a('</body>\n')
|
|
||||||
a('</html>\n')
|
|
||||||
return ''.join(v).encode(encoding, "xmlcharrefreplace")
|
|
||||||
|
|
||||||
|
|
||||||
class different_locale:
|
|
||||||
def __init__(self, locale):
|
|
||||||
self.locale = locale
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.oldlocale = _locale.getlocale(_locale.LC_TIME)
|
|
||||||
_locale.setlocale(_locale.LC_TIME, self.locale)
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
_locale.setlocale(_locale.LC_TIME, self.oldlocale)
|
|
||||||
|
|
||||||
|
|
||||||
class LocaleTextCalendar(TextCalendar):
|
|
||||||
"""
|
|
||||||
This class can be passed a locale name in the constructor and will return
|
|
||||||
month and weekday names in the specified locale. If this locale includes
|
|
||||||
an encoding all strings containing month and weekday names will be returned
|
|
||||||
as unicode.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, firstweekday=0, locale=None):
|
|
||||||
TextCalendar.__init__(self, firstweekday)
|
|
||||||
if locale is None:
|
|
||||||
locale = _locale.getdefaultlocale()
|
|
||||||
self.locale = locale
|
|
||||||
|
|
||||||
def formatweekday(self, day, width):
|
|
||||||
with different_locale(self.locale):
|
|
||||||
if width >= 9:
|
|
||||||
names = day_name
|
|
||||||
else:
|
|
||||||
names = day_abbr
|
|
||||||
name = names[day]
|
|
||||||
return name[:width].center(width)
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, width, withyear=True):
|
|
||||||
with different_locale(self.locale):
|
|
||||||
s = month_name[themonth]
|
|
||||||
if withyear:
|
|
||||||
s = "%s %r" % (s, theyear)
|
|
||||||
return s.center(width)
|
|
||||||
|
|
||||||
|
|
||||||
class LocaleHTMLCalendar(HTMLCalendar):
|
|
||||||
"""
|
|
||||||
This class can be passed a locale name in the constructor and will return
|
|
||||||
month and weekday names in the specified locale. If this locale includes
|
|
||||||
an encoding all strings containing month and weekday names will be returned
|
|
||||||
as unicode.
|
|
||||||
"""
|
|
||||||
def __init__(self, firstweekday=0, locale=None):
|
|
||||||
HTMLCalendar.__init__(self, firstweekday)
|
|
||||||
if locale is None:
|
|
||||||
locale = _locale.getdefaultlocale()
|
|
||||||
self.locale = locale
|
|
||||||
|
|
||||||
def formatweekday(self, day):
|
|
||||||
with different_locale(self.locale):
|
|
||||||
s = day_abbr[day]
|
|
||||||
return '<th class="%s">%s</th>' % (self.cssclasses[day], s)
|
|
||||||
|
|
||||||
def formatmonthname(self, theyear, themonth, withyear=True):
|
|
||||||
with different_locale(self.locale):
|
|
||||||
s = month_name[themonth]
|
|
||||||
if withyear:
|
|
||||||
s = '%s %s' % (s, theyear)
|
|
||||||
return '<tr><th colspan="7" class="month">%s</th></tr>' % s
|
|
||||||
|
|
||||||
|
|
||||||
# Support for old module level interface
|
|
||||||
c = TextCalendar()
|
|
||||||
|
|
||||||
firstweekday = c.getfirstweekday
|
|
||||||
|
|
||||||
def setfirstweekday(firstweekday):
|
|
||||||
if not MONDAY <= firstweekday <= SUNDAY:
|
|
||||||
raise IllegalWeekdayError(firstweekday)
|
|
||||||
c.firstweekday = firstweekday
|
|
||||||
|
|
||||||
monthcalendar = c.monthdayscalendar
|
|
||||||
prweek = c.prweek
|
|
||||||
week = c.formatweek
|
|
||||||
weekheader = c.formatweekheader
|
|
||||||
prmonth = c.prmonth
|
|
||||||
month = c.formatmonth
|
|
||||||
calendar = c.formatyear
|
|
||||||
prcal = c.pryear
|
|
||||||
|
|
||||||
|
|
||||||
# Spacing of month columns for multi-column year calendar
|
|
||||||
_colwidth = 7*3 - 1 # Amount printed by prweek()
|
|
||||||
_spacing = 6 # Number of spaces between columns
|
|
||||||
|
|
||||||
|
|
||||||
def format(cols, colwidth=_colwidth, spacing=_spacing):
|
|
||||||
"""Prints multi-column formatting for year calendars"""
|
|
||||||
print(formatstring(cols, colwidth, spacing))
|
|
||||||
|
|
||||||
|
|
||||||
def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
|
|
||||||
"""Returns a string formatted from n strings, centered within n columns."""
|
|
||||||
spacing *= ' '
|
|
||||||
return spacing.join(c.center(colwidth) for c in cols)
|
|
||||||
|
|
||||||
|
|
||||||
EPOCH = 1970
|
|
||||||
_EPOCH_ORD = datetime.date(EPOCH, 1, 1).toordinal()
|
|
||||||
|
|
||||||
|
|
||||||
def timegm(tuple):
|
|
||||||
"""Unrelated but handy function to calculate Unix timestamp from GMT."""
|
|
||||||
year, month, day, hour, minute, second = tuple[:6]
|
|
||||||
days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
|
|
||||||
hours = days*24 + hour
|
|
||||||
minutes = hours*60 + minute
|
|
||||||
seconds = minutes*60 + second
|
|
||||||
return seconds
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
import argparse
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
textgroup = parser.add_argument_group('text only arguments')
|
|
||||||
htmlgroup = parser.add_argument_group('html only arguments')
|
|
||||||
textgroup.add_argument(
|
|
||||||
"-w", "--width",
|
|
||||||
type=int, default=2,
|
|
||||||
help="width of date column (default 2)"
|
|
||||||
)
|
|
||||||
textgroup.add_argument(
|
|
||||||
"-l", "--lines",
|
|
||||||
type=int, default=1,
|
|
||||||
help="number of lines for each week (default 1)"
|
|
||||||
)
|
|
||||||
textgroup.add_argument(
|
|
||||||
"-s", "--spacing",
|
|
||||||
type=int, default=6,
|
|
||||||
help="spacing between months (default 6)"
|
|
||||||
)
|
|
||||||
textgroup.add_argument(
|
|
||||||
"-m", "--months",
|
|
||||||
type=int, default=3,
|
|
||||||
help="months per row (default 3)"
|
|
||||||
)
|
|
||||||
htmlgroup.add_argument(
|
|
||||||
"-c", "--css",
|
|
||||||
default="calendar.css",
|
|
||||||
help="CSS to use for page"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-L", "--locale",
|
|
||||||
default=None,
|
|
||||||
help="locale to be used from month and weekday names"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-e", "--encoding",
|
|
||||||
default=None,
|
|
||||||
help="encoding to use for output"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-t", "--type",
|
|
||||||
default="text",
|
|
||||||
choices=("text", "html"),
|
|
||||||
help="output type (text or html)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"year",
|
|
||||||
nargs='?', type=int,
|
|
||||||
help="year number (1-9999)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"month",
|
|
||||||
nargs='?', type=int,
|
|
||||||
help="month number (1-12, text only)"
|
|
||||||
)
|
|
||||||
|
|
||||||
options = parser.parse_args(args[1:])
|
|
||||||
|
|
||||||
if options.locale and not options.encoding:
|
|
||||||
parser.error("if --locale is specified --encoding is required")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
locale = options.locale, options.encoding
|
|
||||||
|
|
||||||
if options.type == "html":
|
|
||||||
if options.locale:
|
|
||||||
cal = LocaleHTMLCalendar(locale=locale)
|
|
||||||
else:
|
|
||||||
cal = HTMLCalendar()
|
|
||||||
encoding = options.encoding
|
|
||||||
if encoding is None:
|
|
||||||
encoding = sys.getdefaultencoding()
|
|
||||||
optdict = dict(encoding=encoding, css=options.css)
|
|
||||||
write = sys.stdout.buffer.write
|
|
||||||
if options.year is None:
|
|
||||||
write(cal.formatyearpage(datetime.date.today().year, **optdict))
|
|
||||||
elif options.month is None:
|
|
||||||
write(cal.formatyearpage(options.year, **optdict))
|
|
||||||
else:
|
|
||||||
parser.error("incorrect number of arguments")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
if options.locale:
|
|
||||||
cal = LocaleTextCalendar(locale=locale)
|
|
||||||
else:
|
|
||||||
cal = TextCalendar()
|
|
||||||
optdict = dict(w=options.width, l=options.lines)
|
|
||||||
if options.month is None:
|
|
||||||
optdict["c"] = options.spacing
|
|
||||||
optdict["m"] = options.months
|
|
||||||
if options.year is None:
|
|
||||||
result = cal.formatyear(datetime.date.today().year, **optdict)
|
|
||||||
elif options.month is None:
|
|
||||||
result = cal.formatyear(options.year, **optdict)
|
|
||||||
else:
|
|
||||||
result = cal.formatmonth(options.year, options.month, **optdict)
|
|
||||||
write = sys.stdout.write
|
|
||||||
if options.encoding:
|
|
||||||
result = result.encode(options.encoding)
|
|
||||||
write = sys.stdout.buffer.write
|
|
||||||
write(result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(sys.argv)
|
|
||||||
169
Lib/chunk.py
169
Lib/chunk.py
@@ -1,169 +0,0 @@
|
|||||||
"""Simple class to read IFF chunks.
|
|
||||||
|
|
||||||
An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File
|
|
||||||
Format)) has the following structure:
|
|
||||||
|
|
||||||
+----------------+
|
|
||||||
| ID (4 bytes) |
|
|
||||||
+----------------+
|
|
||||||
| size (4 bytes) |
|
|
||||||
+----------------+
|
|
||||||
| data |
|
|
||||||
| ... |
|
|
||||||
+----------------+
|
|
||||||
|
|
||||||
The ID is a 4-byte string which identifies the type of chunk.
|
|
||||||
|
|
||||||
The size field (a 32-bit value, encoded using big-endian byte order)
|
|
||||||
gives the size of the whole chunk, including the 8-byte header.
|
|
||||||
|
|
||||||
Usually an IFF-type file consists of one or more chunks. The proposed
|
|
||||||
usage of the Chunk class defined here is to instantiate an instance at
|
|
||||||
the start of each chunk and read from the instance until it reaches
|
|
||||||
the end, after which a new instance can be instantiated. At the end
|
|
||||||
of the file, creating a new instance will fail with an EOFError
|
|
||||||
exception.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
chunk = Chunk(file)
|
|
||||||
except EOFError:
|
|
||||||
break
|
|
||||||
chunktype = chunk.getname()
|
|
||||||
while True:
|
|
||||||
data = chunk.read(nbytes)
|
|
||||||
if not data:
|
|
||||||
pass
|
|
||||||
# do something with data
|
|
||||||
|
|
||||||
The interface is file-like. The implemented methods are:
|
|
||||||
read, close, seek, tell, isatty.
|
|
||||||
Extra methods are: skip() (called by close, skips to the end of the chunk),
|
|
||||||
getname() (returns the name (ID) of the chunk)
|
|
||||||
|
|
||||||
The __init__ method has one required argument, a file-like object
|
|
||||||
(including a chunk instance), and one optional argument, a flag which
|
|
||||||
specifies whether or not chunks are aligned on 2-byte boundaries. The
|
|
||||||
default is 1, i.e. aligned.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Chunk:
|
|
||||||
def __init__(self, file, align=True, bigendian=True, inclheader=False):
|
|
||||||
import struct
|
|
||||||
self.closed = False
|
|
||||||
self.align = align # whether to align to word (2-byte) boundaries
|
|
||||||
if bigendian:
|
|
||||||
strflag = '>'
|
|
||||||
else:
|
|
||||||
strflag = '<'
|
|
||||||
self.file = file
|
|
||||||
self.chunkname = file.read(4)
|
|
||||||
if len(self.chunkname) < 4:
|
|
||||||
raise EOFError
|
|
||||||
try:
|
|
||||||
self.chunksize = struct.unpack_from(strflag+'L', file.read(4))[0]
|
|
||||||
except struct.error:
|
|
||||||
raise EOFError
|
|
||||||
if inclheader:
|
|
||||||
self.chunksize = self.chunksize - 8 # subtract header
|
|
||||||
self.size_read = 0
|
|
||||||
try:
|
|
||||||
self.offset = self.file.tell()
|
|
||||||
except (AttributeError, OSError):
|
|
||||||
self.seekable = False
|
|
||||||
else:
|
|
||||||
self.seekable = True
|
|
||||||
|
|
||||||
def getname(self):
|
|
||||||
"""Return the name (ID) of the current chunk."""
|
|
||||||
return self.chunkname
|
|
||||||
|
|
||||||
def getsize(self):
|
|
||||||
"""Return the size of the current chunk."""
|
|
||||||
return self.chunksize
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
if not self.closed:
|
|
||||||
try:
|
|
||||||
self.skip()
|
|
||||||
finally:
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("I/O operation on closed file")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def seek(self, pos, whence=0):
|
|
||||||
"""Seek to specified position into the chunk.
|
|
||||||
Default position is 0 (start of chunk).
|
|
||||||
If the file is not seekable, this will result in an error.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("I/O operation on closed file")
|
|
||||||
if not self.seekable:
|
|
||||||
raise OSError("cannot seek")
|
|
||||||
if whence == 1:
|
|
||||||
pos = pos + self.size_read
|
|
||||||
elif whence == 2:
|
|
||||||
pos = pos + self.chunksize
|
|
||||||
if pos < 0 or pos > self.chunksize:
|
|
||||||
raise RuntimeError
|
|
||||||
self.file.seek(self.offset + pos, 0)
|
|
||||||
self.size_read = pos
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("I/O operation on closed file")
|
|
||||||
return self.size_read
|
|
||||||
|
|
||||||
def read(self, size=-1):
|
|
||||||
"""Read at most size bytes from the chunk.
|
|
||||||
If size is omitted or negative, read until the end
|
|
||||||
of the chunk.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("I/O operation on closed file")
|
|
||||||
if self.size_read >= self.chunksize:
|
|
||||||
return b''
|
|
||||||
if size < 0:
|
|
||||||
size = self.chunksize - self.size_read
|
|
||||||
if size > self.chunksize - self.size_read:
|
|
||||||
size = self.chunksize - self.size_read
|
|
||||||
data = self.file.read(size)
|
|
||||||
self.size_read = self.size_read + len(data)
|
|
||||||
if self.size_read == self.chunksize and \
|
|
||||||
self.align and \
|
|
||||||
(self.chunksize & 1):
|
|
||||||
dummy = self.file.read(1)
|
|
||||||
self.size_read = self.size_read + len(dummy)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
"""Skip the rest of the chunk.
|
|
||||||
If you are not interested in the contents of the chunk,
|
|
||||||
this method should be called so that the file points to
|
|
||||||
the start of the next chunk.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.closed:
|
|
||||||
raise ValueError("I/O operation on closed file")
|
|
||||||
if self.seekable:
|
|
||||||
try:
|
|
||||||
n = self.chunksize - self.size_read
|
|
||||||
# maybe fix alignment
|
|
||||||
if self.align and (self.chunksize & 1):
|
|
||||||
n = n + 1
|
|
||||||
self.file.seek(n, 1)
|
|
||||||
self.size_read = self.size_read + n
|
|
||||||
return
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
while self.size_read < self.chunksize:
|
|
||||||
n = min(8192, self.chunksize - self.size_read)
|
|
||||||
dummy = self.read(n)
|
|
||||||
if not dummy:
|
|
||||||
raise EOFError
|
|
||||||
401
Lib/cmd.py
401
Lib/cmd.py
@@ -1,401 +0,0 @@
|
|||||||
"""A generic class to build line-oriented command interpreters.
|
|
||||||
|
|
||||||
Interpreters constructed with this class obey the following conventions:
|
|
||||||
|
|
||||||
1. End of file on input is processed as the command 'EOF'.
|
|
||||||
2. A command is parsed out of each line by collecting the prefix composed
|
|
||||||
of characters in the identchars member.
|
|
||||||
3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method
|
|
||||||
is passed a single argument consisting of the remainder of the line.
|
|
||||||
4. Typing an empty line repeats the last command. (Actually, it calls the
|
|
||||||
method `emptyline', which may be overridden in a subclass.)
|
|
||||||
5. There is a predefined `help' method. Given an argument `topic', it
|
|
||||||
calls the command `help_topic'. With no arguments, it lists all topics
|
|
||||||
with defined help_ functions, broken into up to three topics; documented
|
|
||||||
commands, miscellaneous help topics, and undocumented commands.
|
|
||||||
6. The command '?' is a synonym for `help'. The command '!' is a synonym
|
|
||||||
for `shell', if a do_shell method exists.
|
|
||||||
7. If completion is enabled, completing commands will be done automatically,
|
|
||||||
and completing of commands args is done by calling complete_foo() with
|
|
||||||
arguments text, line, begidx, endidx. text is string we are matching
|
|
||||||
against, all returned matches must begin with it. line is the current
|
|
||||||
input line (lstripped), begidx and endidx are the beginning and end
|
|
||||||
indexes of the text being matched, which could be used to provide
|
|
||||||
different completion depending upon which position the argument is in.
|
|
||||||
|
|
||||||
The `default' method may be overridden to intercept commands for which there
|
|
||||||
is no do_ method.
|
|
||||||
|
|
||||||
The `completedefault' method may be overridden to intercept completions for
|
|
||||||
commands that have no complete_ method.
|
|
||||||
|
|
||||||
The data member `self.ruler' sets the character used to draw separator lines
|
|
||||||
in the help messages. If empty, no ruler line is drawn. It defaults to "=".
|
|
||||||
|
|
||||||
If the value of `self.intro' is nonempty when the cmdloop method is called,
|
|
||||||
it is printed out on interpreter startup. This value may be overridden
|
|
||||||
via an optional argument to the cmdloop() method.
|
|
||||||
|
|
||||||
The data members `self.doc_header', `self.misc_header', and
|
|
||||||
`self.undoc_header' set the headers used for the help function's
|
|
||||||
listings of documented functions, miscellaneous topics, and undocumented
|
|
||||||
functions respectively.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import string, sys
|
|
||||||
|
|
||||||
__all__ = ["Cmd"]
|
|
||||||
|
|
||||||
PROMPT = '(Cmd) '
|
|
||||||
IDENTCHARS = string.ascii_letters + string.digits + '_'
|
|
||||||
|
|
||||||
class Cmd:
|
|
||||||
"""A simple framework for writing line-oriented command interpreters.
|
|
||||||
|
|
||||||
These are often useful for test harnesses, administrative tools, and
|
|
||||||
prototypes that will later be wrapped in a more sophisticated interface.
|
|
||||||
|
|
||||||
A Cmd instance or subclass instance is a line-oriented interpreter
|
|
||||||
framework. There is no good reason to instantiate Cmd itself; rather,
|
|
||||||
it's useful as a superclass of an interpreter class you define yourself
|
|
||||||
in order to inherit Cmd's methods and encapsulate action methods.
|
|
||||||
|
|
||||||
"""
|
|
||||||
prompt = PROMPT
|
|
||||||
identchars = IDENTCHARS
|
|
||||||
ruler = '='
|
|
||||||
lastcmd = ''
|
|
||||||
intro = None
|
|
||||||
doc_leader = ""
|
|
||||||
doc_header = "Documented commands (type help <topic>):"
|
|
||||||
misc_header = "Miscellaneous help topics:"
|
|
||||||
undoc_header = "Undocumented commands:"
|
|
||||||
nohelp = "*** No help on %s"
|
|
||||||
use_rawinput = 1
|
|
||||||
|
|
||||||
def __init__(self, completekey='tab', stdin=None, stdout=None):
|
|
||||||
"""Instantiate a line-oriented interpreter framework.
|
|
||||||
|
|
||||||
The optional argument 'completekey' is the readline name of a
|
|
||||||
completion key; it defaults to the Tab key. If completekey is
|
|
||||||
not None and the readline module is available, command completion
|
|
||||||
is done automatically. The optional arguments stdin and stdout
|
|
||||||
specify alternate input and output file objects; if not specified,
|
|
||||||
sys.stdin and sys.stdout are used.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if stdin is not None:
|
|
||||||
self.stdin = stdin
|
|
||||||
else:
|
|
||||||
self.stdin = sys.stdin
|
|
||||||
if stdout is not None:
|
|
||||||
self.stdout = stdout
|
|
||||||
else:
|
|
||||||
self.stdout = sys.stdout
|
|
||||||
self.cmdqueue = []
|
|
||||||
self.completekey = completekey
|
|
||||||
|
|
||||||
def cmdloop(self, intro=None):
|
|
||||||
"""Repeatedly issue a prompt, accept input, parse an initial prefix
|
|
||||||
off the received input, and dispatch to action methods, passing them
|
|
||||||
the remainder of the line as argument.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.preloop()
|
|
||||||
if self.use_rawinput and self.completekey:
|
|
||||||
try:
|
|
||||||
import readline
|
|
||||||
self.old_completer = readline.get_completer()
|
|
||||||
readline.set_completer(self.complete)
|
|
||||||
readline.parse_and_bind(self.completekey+": complete")
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if intro is not None:
|
|
||||||
self.intro = intro
|
|
||||||
if self.intro:
|
|
||||||
self.stdout.write(str(self.intro)+"\n")
|
|
||||||
stop = None
|
|
||||||
while not stop:
|
|
||||||
if self.cmdqueue:
|
|
||||||
line = self.cmdqueue.pop(0)
|
|
||||||
else:
|
|
||||||
if self.use_rawinput:
|
|
||||||
try:
|
|
||||||
line = input(self.prompt)
|
|
||||||
except EOFError:
|
|
||||||
line = 'EOF'
|
|
||||||
else:
|
|
||||||
self.stdout.write(self.prompt)
|
|
||||||
self.stdout.flush()
|
|
||||||
line = self.stdin.readline()
|
|
||||||
if not len(line):
|
|
||||||
line = 'EOF'
|
|
||||||
else:
|
|
||||||
line = line.rstrip('\r\n')
|
|
||||||
line = self.precmd(line)
|
|
||||||
stop = self.onecmd(line)
|
|
||||||
stop = self.postcmd(stop, line)
|
|
||||||
self.postloop()
|
|
||||||
finally:
|
|
||||||
if self.use_rawinput and self.completekey:
|
|
||||||
try:
|
|
||||||
import readline
|
|
||||||
readline.set_completer(self.old_completer)
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def precmd(self, line):
|
|
||||||
"""Hook method executed just before the command line is
|
|
||||||
interpreted, but after the input prompt is generated and issued.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return line
|
|
||||||
|
|
||||||
def postcmd(self, stop, line):
|
|
||||||
"""Hook method executed just after a command dispatch is finished."""
|
|
||||||
return stop
|
|
||||||
|
|
||||||
def preloop(self):
|
|
||||||
"""Hook method executed once when the cmdloop() method is called."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def postloop(self):
|
|
||||||
"""Hook method executed once when the cmdloop() method is about to
|
|
||||||
return.
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def parseline(self, line):
|
|
||||||
"""Parse the line into a command name and a string containing
|
|
||||||
the arguments. Returns a tuple containing (command, args, line).
|
|
||||||
'command' and 'args' may be None if the line couldn't be parsed.
|
|
||||||
"""
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
return None, None, line
|
|
||||||
elif line[0] == '?':
|
|
||||||
line = 'help ' + line[1:]
|
|
||||||
elif line[0] == '!':
|
|
||||||
if hasattr(self, 'do_shell'):
|
|
||||||
line = 'shell ' + line[1:]
|
|
||||||
else:
|
|
||||||
return None, None, line
|
|
||||||
i, n = 0, len(line)
|
|
||||||
while i < n and line[i] in self.identchars: i = i+1
|
|
||||||
cmd, arg = line[:i], line[i:].strip()
|
|
||||||
return cmd, arg, line
|
|
||||||
|
|
||||||
def onecmd(self, line):
|
|
||||||
"""Interpret the argument as though it had been typed in response
|
|
||||||
to the prompt.
|
|
||||||
|
|
||||||
This may be overridden, but should not normally need to be;
|
|
||||||
see the precmd() and postcmd() methods for useful execution hooks.
|
|
||||||
The return value is a flag indicating whether interpretation of
|
|
||||||
commands by the interpreter should stop.
|
|
||||||
|
|
||||||
"""
|
|
||||||
cmd, arg, line = self.parseline(line)
|
|
||||||
if not line:
|
|
||||||
return self.emptyline()
|
|
||||||
if cmd is None:
|
|
||||||
return self.default(line)
|
|
||||||
self.lastcmd = line
|
|
||||||
if line == 'EOF' :
|
|
||||||
self.lastcmd = ''
|
|
||||||
if cmd == '':
|
|
||||||
return self.default(line)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
func = getattr(self, 'do_' + cmd)
|
|
||||||
except AttributeError:
|
|
||||||
return self.default(line)
|
|
||||||
return func(arg)
|
|
||||||
|
|
||||||
def emptyline(self):
|
|
||||||
"""Called when an empty line is entered in response to the prompt.
|
|
||||||
|
|
||||||
If this method is not overridden, it repeats the last nonempty
|
|
||||||
command entered.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if self.lastcmd:
|
|
||||||
return self.onecmd(self.lastcmd)
|
|
||||||
|
|
||||||
def default(self, line):
|
|
||||||
"""Called on an input line when the command prefix is not recognized.
|
|
||||||
|
|
||||||
If this method is not overridden, it prints an error message and
|
|
||||||
returns.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.stdout.write('*** Unknown syntax: %s\n'%line)
|
|
||||||
|
|
||||||
def completedefault(self, *ignored):
|
|
||||||
"""Method called to complete an input line when no command-specific
|
|
||||||
complete_*() method is available.
|
|
||||||
|
|
||||||
By default, it returns an empty list.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
def completenames(self, text, *ignored):
|
|
||||||
dotext = 'do_'+text
|
|
||||||
return [a[3:] for a in self.get_names() if a.startswith(dotext)]
|
|
||||||
|
|
||||||
def complete(self, text, state):
|
|
||||||
"""Return the next possible completion for 'text'.
|
|
||||||
|
|
||||||
If a command has not been entered, then complete against command list.
|
|
||||||
Otherwise try to call complete_<command> to get list of completions.
|
|
||||||
"""
|
|
||||||
if state == 0:
|
|
||||||
import readline
|
|
||||||
origline = readline.get_line_buffer()
|
|
||||||
line = origline.lstrip()
|
|
||||||
stripped = len(origline) - len(line)
|
|
||||||
begidx = readline.get_begidx() - stripped
|
|
||||||
endidx = readline.get_endidx() - stripped
|
|
||||||
if begidx>0:
|
|
||||||
cmd, args, foo = self.parseline(line)
|
|
||||||
if cmd == '':
|
|
||||||
compfunc = self.completedefault
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
compfunc = getattr(self, 'complete_' + cmd)
|
|
||||||
except AttributeError:
|
|
||||||
compfunc = self.completedefault
|
|
||||||
else:
|
|
||||||
compfunc = self.completenames
|
|
||||||
self.completion_matches = compfunc(text, line, begidx, endidx)
|
|
||||||
try:
|
|
||||||
return self.completion_matches[state]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_names(self):
|
|
||||||
# This method used to pull in base class attributes
|
|
||||||
# at a time dir() didn't do it yet.
|
|
||||||
return dir(self.__class__)
|
|
||||||
|
|
||||||
def complete_help(self, *args):
|
|
||||||
commands = set(self.completenames(*args))
|
|
||||||
topics = set(a[5:] for a in self.get_names()
|
|
||||||
if a.startswith('help_' + args[0]))
|
|
||||||
return list(commands | topics)
|
|
||||||
|
|
||||||
def do_help(self, arg):
|
|
||||||
'List available commands with "help" or detailed help with "help cmd".'
|
|
||||||
if arg:
|
|
||||||
# XXX check arg syntax
|
|
||||||
try:
|
|
||||||
func = getattr(self, 'help_' + arg)
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
doc=getattr(self, 'do_' + arg).__doc__
|
|
||||||
if doc:
|
|
||||||
self.stdout.write("%s\n"%str(doc))
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.stdout.write("%s\n"%str(self.nohelp % (arg,)))
|
|
||||||
return
|
|
||||||
func()
|
|
||||||
else:
|
|
||||||
names = self.get_names()
|
|
||||||
cmds_doc = []
|
|
||||||
cmds_undoc = []
|
|
||||||
help = {}
|
|
||||||
for name in names:
|
|
||||||
if name[:5] == 'help_':
|
|
||||||
help[name[5:]]=1
|
|
||||||
names.sort()
|
|
||||||
# There can be duplicates if routines overridden
|
|
||||||
prevname = ''
|
|
||||||
for name in names:
|
|
||||||
if name[:3] == 'do_':
|
|
||||||
if name == prevname:
|
|
||||||
continue
|
|
||||||
prevname = name
|
|
||||||
cmd=name[3:]
|
|
||||||
if cmd in help:
|
|
||||||
cmds_doc.append(cmd)
|
|
||||||
del help[cmd]
|
|
||||||
elif getattr(self, name).__doc__:
|
|
||||||
cmds_doc.append(cmd)
|
|
||||||
else:
|
|
||||||
cmds_undoc.append(cmd)
|
|
||||||
self.stdout.write("%s\n"%str(self.doc_leader))
|
|
||||||
self.print_topics(self.doc_header, cmds_doc, 15,80)
|
|
||||||
self.print_topics(self.misc_header, list(help.keys()),15,80)
|
|
||||||
self.print_topics(self.undoc_header, cmds_undoc, 15,80)
|
|
||||||
|
|
||||||
def print_topics(self, header, cmds, cmdlen, maxcol):
|
|
||||||
if cmds:
|
|
||||||
self.stdout.write("%s\n"%str(header))
|
|
||||||
if self.ruler:
|
|
||||||
self.stdout.write("%s\n"%str(self.ruler * len(header)))
|
|
||||||
self.columnize(cmds, maxcol-1)
|
|
||||||
self.stdout.write("\n")
|
|
||||||
|
|
||||||
def columnize(self, list, displaywidth=80):
|
|
||||||
"""Display a list of strings as a compact set of columns.
|
|
||||||
|
|
||||||
Each column is only as wide as necessary.
|
|
||||||
Columns are separated by two spaces (one was not legible enough).
|
|
||||||
"""
|
|
||||||
if not list:
|
|
||||||
self.stdout.write("<empty>\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
nonstrings = [i for i in range(len(list))
|
|
||||||
if not isinstance(list[i], str)]
|
|
||||||
if nonstrings:
|
|
||||||
raise TypeError("list[i] not a string for i in %s"
|
|
||||||
% ", ".join(map(str, nonstrings)))
|
|
||||||
size = len(list)
|
|
||||||
if size == 1:
|
|
||||||
self.stdout.write('%s\n'%str(list[0]))
|
|
||||||
return
|
|
||||||
# Try every row count from 1 upwards
|
|
||||||
for nrows in range(1, len(list)):
|
|
||||||
ncols = (size+nrows-1) // nrows
|
|
||||||
colwidths = []
|
|
||||||
totwidth = -2
|
|
||||||
for col in range(ncols):
|
|
||||||
colwidth = 0
|
|
||||||
for row in range(nrows):
|
|
||||||
i = row + nrows*col
|
|
||||||
if i >= size:
|
|
||||||
break
|
|
||||||
x = list[i]
|
|
||||||
colwidth = max(colwidth, len(x))
|
|
||||||
colwidths.append(colwidth)
|
|
||||||
totwidth += colwidth + 2
|
|
||||||
if totwidth > displaywidth:
|
|
||||||
break
|
|
||||||
if totwidth <= displaywidth:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
nrows = len(list)
|
|
||||||
ncols = 1
|
|
||||||
colwidths = [0]
|
|
||||||
for row in range(nrows):
|
|
||||||
texts = []
|
|
||||||
for col in range(ncols):
|
|
||||||
i = row + nrows*col
|
|
||||||
if i >= size:
|
|
||||||
x = ""
|
|
||||||
else:
|
|
||||||
x = list[i]
|
|
||||||
texts.append(x)
|
|
||||||
while texts and not texts[-1]:
|
|
||||||
del texts[-1]
|
|
||||||
for col in range(len(texts)):
|
|
||||||
texts[col] = texts[col].ljust(colwidths[col])
|
|
||||||
self.stdout.write("%s\n"%str(" ".join(texts)))
|
|
||||||
314
Lib/code.py
314
Lib/code.py
@@ -1,314 +0,0 @@
|
|||||||
"""Utilities needed to emulate Python's interactive interpreter.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Inspired by similar code by Jeff Epler and Fredrik Lundh.
|
|
||||||
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import argparse
|
|
||||||
from codeop import CommandCompiler, compile_command
|
|
||||||
|
|
||||||
__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
|
|
||||||
"compile_command"]
|
|
||||||
|
|
||||||
class InteractiveInterpreter:
|
|
||||||
"""Base class for InteractiveConsole.
|
|
||||||
|
|
||||||
This class deals with parsing and interpreter state (the user's
|
|
||||||
namespace); it doesn't deal with input buffering or prompting or
|
|
||||||
input file naming (the filename is always passed in explicitly).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, locals=None):
|
|
||||||
"""Constructor.
|
|
||||||
|
|
||||||
The optional 'locals' argument specifies the dictionary in
|
|
||||||
which code will be executed; it defaults to a newly created
|
|
||||||
dictionary with key "__name__" set to "__console__" and key
|
|
||||||
"__doc__" set to None.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if locals is None:
|
|
||||||
locals = {"__name__": "__console__", "__doc__": None}
|
|
||||||
self.locals = locals
|
|
||||||
self.compile = CommandCompiler()
|
|
||||||
|
|
||||||
def runsource(self, source, filename="<input>", symbol="single"):
|
|
||||||
"""Compile and run some source in the interpreter.
|
|
||||||
|
|
||||||
Arguments are as for compile_command().
|
|
||||||
|
|
||||||
One several things can happen:
|
|
||||||
|
|
||||||
1) The input is incorrect; compile_command() raised an
|
|
||||||
exception (SyntaxError or OverflowError). A syntax traceback
|
|
||||||
will be printed by calling the showsyntaxerror() method.
|
|
||||||
|
|
||||||
2) The input is incomplete, and more input is required;
|
|
||||||
compile_command() returned None. Nothing happens.
|
|
||||||
|
|
||||||
3) The input is complete; compile_command() returned a code
|
|
||||||
object. The code is executed by calling self.runcode() (which
|
|
||||||
also handles run-time exceptions, except for SystemExit).
|
|
||||||
|
|
||||||
The return value is True in case 2, False in the other cases (unless
|
|
||||||
an exception is raised). The return value can be used to
|
|
||||||
decide whether to use sys.ps1 or sys.ps2 to prompt the next
|
|
||||||
line.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
code = self.compile(source, filename, symbol)
|
|
||||||
except (OverflowError, SyntaxError, ValueError):
|
|
||||||
# Case 1
|
|
||||||
self.showsyntaxerror(filename)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if code is None:
|
|
||||||
# Case 2
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Case 3
|
|
||||||
self.runcode(code)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def runcode(self, code):
|
|
||||||
"""Execute a code object.
|
|
||||||
|
|
||||||
When an exception occurs, self.showtraceback() is called to
|
|
||||||
display a traceback. All exceptions are caught except
|
|
||||||
SystemExit, which is reraised.
|
|
||||||
|
|
||||||
A note about KeyboardInterrupt: this exception may occur
|
|
||||||
elsewhere in this code, and may not always be caught. The
|
|
||||||
caller should be prepared to deal with it.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
exec(code, self.locals)
|
|
||||||
except SystemExit:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.showtraceback()
|
|
||||||
|
|
||||||
def showsyntaxerror(self, filename=None):
|
|
||||||
"""Display the syntax error that just occurred.
|
|
||||||
|
|
||||||
This doesn't display a stack trace because there isn't one.
|
|
||||||
|
|
||||||
If a filename is given, it is stuffed in the exception instead
|
|
||||||
of what was there before (because Python's parser always uses
|
|
||||||
"<string>" when reading from a string).
|
|
||||||
|
|
||||||
The output is written by self.write(), below.
|
|
||||||
|
|
||||||
"""
|
|
||||||
type, value, tb = sys.exc_info()
|
|
||||||
sys.last_type = type
|
|
||||||
sys.last_value = value
|
|
||||||
sys.last_traceback = tb
|
|
||||||
if filename and type is SyntaxError:
|
|
||||||
# Work hard to stuff the correct filename in the exception
|
|
||||||
try:
|
|
||||||
msg, (dummy_filename, lineno, offset, line) = value.args
|
|
||||||
except ValueError:
|
|
||||||
# Not the format we expect; leave it alone
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Stuff in the right filename
|
|
||||||
value = SyntaxError(msg, (filename, lineno, offset, line))
|
|
||||||
sys.last_value = value
|
|
||||||
if sys.excepthook is sys.__excepthook__:
|
|
||||||
lines = traceback.format_exception_only(type, value)
|
|
||||||
self.write(''.join(lines))
|
|
||||||
else:
|
|
||||||
# If someone has set sys.excepthook, we let that take precedence
|
|
||||||
# over self.write
|
|
||||||
sys.excepthook(type, value, tb)
|
|
||||||
|
|
||||||
def showtraceback(self):
|
|
||||||
"""Display the exception that just occurred.
|
|
||||||
|
|
||||||
We remove the first stack item because it is our own code.
|
|
||||||
|
|
||||||
The output is written by self.write(), below.
|
|
||||||
|
|
||||||
"""
|
|
||||||
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
|
|
||||||
sys.last_traceback = last_tb
|
|
||||||
try:
|
|
||||||
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
|
|
||||||
if sys.excepthook is sys.__excepthook__:
|
|
||||||
self.write(''.join(lines))
|
|
||||||
else:
|
|
||||||
# If someone has set sys.excepthook, we let that take precedence
|
|
||||||
# over self.write
|
|
||||||
sys.excepthook(ei[0], ei[1], last_tb)
|
|
||||||
finally:
|
|
||||||
last_tb = ei = None
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
"""Write a string.
|
|
||||||
|
|
||||||
The base implementation writes to sys.stderr; a subclass may
|
|
||||||
replace this with a different implementation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
sys.stderr.write(data)
|
|
||||||
|
|
||||||
|
|
||||||
class InteractiveConsole(InteractiveInterpreter):
|
|
||||||
"""Closely emulate the behavior of the interactive Python interpreter.
|
|
||||||
|
|
||||||
This class builds on InteractiveInterpreter and adds prompting
|
|
||||||
using the familiar sys.ps1 and sys.ps2, and input buffering.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, locals=None, filename="<console>"):
|
|
||||||
"""Constructor.
|
|
||||||
|
|
||||||
The optional locals argument will be passed to the
|
|
||||||
InteractiveInterpreter base class.
|
|
||||||
|
|
||||||
The optional filename argument should specify the (file)name
|
|
||||||
of the input stream; it will show up in tracebacks.
|
|
||||||
|
|
||||||
"""
|
|
||||||
InteractiveInterpreter.__init__(self, locals)
|
|
||||||
self.filename = filename
|
|
||||||
self.resetbuffer()
|
|
||||||
|
|
||||||
def resetbuffer(self):
|
|
||||||
"""Reset the input buffer."""
|
|
||||||
self.buffer = []
|
|
||||||
|
|
||||||
def interact(self, banner=None, exitmsg=None):
|
|
||||||
"""Closely emulate the interactive Python console.
|
|
||||||
|
|
||||||
The optional banner argument specifies the banner to print
|
|
||||||
before the first interaction; by default it prints a banner
|
|
||||||
similar to the one printed by the real Python interpreter,
|
|
||||||
followed by the current class name in parentheses (so as not
|
|
||||||
to confuse this with the real interpreter -- since it's so
|
|
||||||
close!).
|
|
||||||
|
|
||||||
The optional exitmsg argument specifies the exit message
|
|
||||||
printed when exiting. Pass the empty string to suppress
|
|
||||||
printing an exit message. If exitmsg is not given or None,
|
|
||||||
a default message is printed.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
sys.ps1
|
|
||||||
except AttributeError:
|
|
||||||
sys.ps1 = ">>> "
|
|
||||||
try:
|
|
||||||
sys.ps2
|
|
||||||
except AttributeError:
|
|
||||||
sys.ps2 = "... "
|
|
||||||
cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
|
|
||||||
if banner is None:
|
|
||||||
self.write("Python %s on %s\n%s\n(%s)\n" %
|
|
||||||
(sys.version, sys.platform, cprt,
|
|
||||||
self.__class__.__name__))
|
|
||||||
elif banner:
|
|
||||||
self.write("%s\n" % str(banner))
|
|
||||||
more = 0
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
if more:
|
|
||||||
prompt = sys.ps2
|
|
||||||
else:
|
|
||||||
prompt = sys.ps1
|
|
||||||
try:
|
|
||||||
line = self.raw_input(prompt)
|
|
||||||
except EOFError:
|
|
||||||
self.write("\n")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
more = self.push(line)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.write("\nKeyboardInterrupt\n")
|
|
||||||
self.resetbuffer()
|
|
||||||
more = 0
|
|
||||||
if exitmsg is None:
|
|
||||||
self.write('now exiting %s...\n' % self.__class__.__name__)
|
|
||||||
elif exitmsg != '':
|
|
||||||
self.write('%s\n' % exitmsg)
|
|
||||||
|
|
||||||
def push(self, line):
|
|
||||||
"""Push a line to the interpreter.
|
|
||||||
|
|
||||||
The line should not have a trailing newline; it may have
|
|
||||||
internal newlines. The line is appended to a buffer and the
|
|
||||||
interpreter's runsource() method is called with the
|
|
||||||
concatenated contents of the buffer as source. If this
|
|
||||||
indicates that the command was executed or invalid, the buffer
|
|
||||||
is reset; otherwise, the command is incomplete, and the buffer
|
|
||||||
is left as it was after the line was appended. The return
|
|
||||||
value is 1 if more input is required, 0 if the line was dealt
|
|
||||||
with in some way (this is the same as runsource()).
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.buffer.append(line)
|
|
||||||
source = "\n".join(self.buffer)
|
|
||||||
more = self.runsource(source, self.filename)
|
|
||||||
if not more:
|
|
||||||
self.resetbuffer()
|
|
||||||
return more
|
|
||||||
|
|
||||||
def raw_input(self, prompt=""):
|
|
||||||
"""Write a prompt and read a line.
|
|
||||||
|
|
||||||
The returned line does not include the trailing newline.
|
|
||||||
When the user enters the EOF key sequence, EOFError is raised.
|
|
||||||
|
|
||||||
The base implementation uses the built-in function
|
|
||||||
input(); a subclass may replace this with a different
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return input(prompt)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def interact(banner=None, readfunc=None, local=None, exitmsg=None):
|
|
||||||
"""Closely emulate the interactive Python interpreter.
|
|
||||||
|
|
||||||
This is a backwards compatible interface to the InteractiveConsole
|
|
||||||
class. When readfunc is not specified, it attempts to import the
|
|
||||||
readline module to enable GNU readline if it is available.
|
|
||||||
|
|
||||||
Arguments (all optional, all default to None):
|
|
||||||
|
|
||||||
banner -- passed to InteractiveConsole.interact()
|
|
||||||
readfunc -- if not None, replaces InteractiveConsole.raw_input()
|
|
||||||
local -- passed to InteractiveInterpreter.__init__()
|
|
||||||
exitmsg -- passed to InteractiveConsole.interact()
|
|
||||||
|
|
||||||
"""
|
|
||||||
console = InteractiveConsole(local)
|
|
||||||
if readfunc is not None:
|
|
||||||
console.raw_input = readfunc
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import readline
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
console.interact(banner, exitmsg)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('-q', action='store_true',
|
|
||||||
help="don't print version and copyright messages")
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.q or sys.flags.quiet:
|
|
||||||
banner = ''
|
|
||||||
else:
|
|
||||||
banner = None
|
|
||||||
interact(banner)
|
|
||||||
1114
Lib/codecs.py
1114
Lib/codecs.py
File diff suppressed because it is too large
Load Diff
168
Lib/codeop.py
168
Lib/codeop.py
@@ -1,168 +0,0 @@
|
|||||||
r"""Utilities to compile possibly incomplete Python source code.
|
|
||||||
|
|
||||||
This module provides two interfaces, broadly similar to the builtin
|
|
||||||
function compile(), which take program text, a filename and a 'mode'
|
|
||||||
and:
|
|
||||||
|
|
||||||
- Return code object if the command is complete and valid
|
|
||||||
- Return None if the command is incomplete
|
|
||||||
- Raise SyntaxError, ValueError or OverflowError if the command is a
|
|
||||||
syntax error (OverflowError and ValueError can be produced by
|
|
||||||
malformed literals).
|
|
||||||
|
|
||||||
Approach:
|
|
||||||
|
|
||||||
First, check if the source consists entirely of blank lines and
|
|
||||||
comments; if so, replace it with 'pass', because the built-in
|
|
||||||
parser doesn't always do the right thing for these.
|
|
||||||
|
|
||||||
Compile three times: as is, with \n, and with \n\n appended. If it
|
|
||||||
compiles as is, it's complete. If it compiles with one \n appended,
|
|
||||||
we expect more. If it doesn't compile either way, we compare the
|
|
||||||
error we get when compiling with \n or \n\n appended. If the errors
|
|
||||||
are the same, the code is broken. But if the errors are different, we
|
|
||||||
expect more. Not intuitive; not even guaranteed to hold in future
|
|
||||||
releases; but this matches the compiler's behavior from Python 1.4
|
|
||||||
through 2.2, at least.
|
|
||||||
|
|
||||||
Caveat:
|
|
||||||
|
|
||||||
It is possible (but not likely) that the parser stops parsing with a
|
|
||||||
successful outcome before reaching the end of the source; in this
|
|
||||||
case, trailing symbols may be ignored instead of causing an error.
|
|
||||||
For example, a backslash followed by two newlines may be followed by
|
|
||||||
arbitrary garbage. This will be fixed once the API for the parser is
|
|
||||||
better.
|
|
||||||
|
|
||||||
The two interfaces are:
|
|
||||||
|
|
||||||
compile_command(source, filename, symbol):
|
|
||||||
|
|
||||||
Compiles a single command in the manner described above.
|
|
||||||
|
|
||||||
CommandCompiler():
|
|
||||||
|
|
||||||
Instances of this class have __call__ methods identical in
|
|
||||||
signature to compile_command; the difference is that if the
|
|
||||||
instance compiles program text containing a __future__ statement,
|
|
||||||
the instance 'remembers' and compiles all subsequent program texts
|
|
||||||
with the statement in force.
|
|
||||||
|
|
||||||
The module also provides another class:
|
|
||||||
|
|
||||||
Compile():
|
|
||||||
|
|
||||||
Instances of this class act like the built-in function compile,
|
|
||||||
but with 'memory' in the sense described above.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import __future__
|
|
||||||
|
|
||||||
_features = [getattr(__future__, fname)
|
|
||||||
for fname in __future__.all_feature_names]
|
|
||||||
|
|
||||||
__all__ = ["compile_command", "Compile", "CommandCompiler"]
|
|
||||||
|
|
||||||
PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h
|
|
||||||
|
|
||||||
def _maybe_compile(compiler, source, filename, symbol):
|
|
||||||
# Check for source consisting of only blank lines and comments
|
|
||||||
for line in source.split("\n"):
|
|
||||||
line = line.strip()
|
|
||||||
if line and line[0] != '#':
|
|
||||||
break # Leave it alone
|
|
||||||
else:
|
|
||||||
if symbol != "eval":
|
|
||||||
source = "pass" # Replace it with a 'pass' statement
|
|
||||||
|
|
||||||
err = err1 = err2 = None
|
|
||||||
code = code1 = code2 = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
code = compiler(source, filename, symbol)
|
|
||||||
except SyntaxError as err:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
code1 = compiler(source + "\n", filename, symbol)
|
|
||||||
except SyntaxError as e:
|
|
||||||
err1 = e
|
|
||||||
|
|
||||||
try:
|
|
||||||
code2 = compiler(source + "\n\n", filename, symbol)
|
|
||||||
except SyntaxError as e:
|
|
||||||
err2 = e
|
|
||||||
|
|
||||||
if code:
|
|
||||||
return code
|
|
||||||
if not code1 and repr(err1) == repr(err2):
|
|
||||||
raise err1
|
|
||||||
|
|
||||||
def _compile(source, filename, symbol):
|
|
||||||
return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)
|
|
||||||
|
|
||||||
def compile_command(source, filename="<input>", symbol="single"):
|
|
||||||
r"""Compile a command and determine whether it is incomplete.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
source -- the source string; may contain \n characters
|
|
||||||
filename -- optional filename from which source was read; default
|
|
||||||
"<input>"
|
|
||||||
symbol -- optional grammar start symbol; "single" (default) or "eval"
|
|
||||||
|
|
||||||
Return value / exceptions raised:
|
|
||||||
|
|
||||||
- Return a code object if the command is complete and valid
|
|
||||||
- Return None if the command is incomplete
|
|
||||||
- Raise SyntaxError, ValueError or OverflowError if the command is a
|
|
||||||
syntax error (OverflowError and ValueError can be produced by
|
|
||||||
malformed literals).
|
|
||||||
"""
|
|
||||||
return _maybe_compile(_compile, source, filename, symbol)
|
|
||||||
|
|
||||||
class Compile:
|
|
||||||
"""Instances of this class behave much like the built-in compile
|
|
||||||
function, but if one is used to compile text containing a future
|
|
||||||
statement, it "remembers" and compiles all subsequent program texts
|
|
||||||
with the statement in force."""
|
|
||||||
def __init__(self):
|
|
||||||
self.flags = PyCF_DONT_IMPLY_DEDENT
|
|
||||||
|
|
||||||
def __call__(self, source, filename, symbol):
|
|
||||||
codeob = compile(source, filename, symbol, self.flags, 1)
|
|
||||||
for feature in _features:
|
|
||||||
if codeob.co_flags & feature.compiler_flag:
|
|
||||||
self.flags |= feature.compiler_flag
|
|
||||||
return codeob
|
|
||||||
|
|
||||||
class CommandCompiler:
|
|
||||||
"""Instances of this class have __call__ methods identical in
|
|
||||||
signature to compile_command; the difference is that if the
|
|
||||||
instance compiles program text containing a __future__ statement,
|
|
||||||
the instance 'remembers' and compiles all subsequent program texts
|
|
||||||
with the statement in force."""
|
|
||||||
|
|
||||||
def __init__(self,):
|
|
||||||
self.compiler = Compile()
|
|
||||||
|
|
||||||
def __call__(self, source, filename="<input>", symbol="single"):
|
|
||||||
r"""Compile a command and determine whether it is incomplete.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
|
|
||||||
source -- the source string; may contain \n characters
|
|
||||||
filename -- optional filename from which source was read;
|
|
||||||
default "<input>"
|
|
||||||
symbol -- optional grammar start symbol; "single" (default) or
|
|
||||||
"eval"
|
|
||||||
|
|
||||||
Return value / exceptions raised:
|
|
||||||
|
|
||||||
- Return a code object if the command is complete and valid
|
|
||||||
- Return None if the command is incomplete
|
|
||||||
- Raise SyntaxError, ValueError or OverflowError if the command is a
|
|
||||||
syntax error (OverflowError and ValueError can be produced by
|
|
||||||
malformed literals).
|
|
||||||
"""
|
|
||||||
return _maybe_compile(self.compiler, source, filename, symbol)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
class defaultdict(dict):
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
if len(args) >= 1:
|
|
||||||
default_factory = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
else:
|
|
||||||
default_factory = None
|
|
||||||
self = dict.__new__(cls, *args, **kwargs)
|
|
||||||
self.default_factory = default_factory
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __missing__(self, key):
|
|
||||||
if self.default_factory:
|
|
||||||
return self.default_factory()
|
|
||||||
else:
|
|
||||||
raise KeyError(key)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"defaultdict({self.default_factory}, {dict.__repr__(self)})"
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
from _collections_abc import *
|
|
||||||
from _collections_abc import __all__
|
|
||||||
152
Lib/colorsys.py
152
Lib/colorsys.py
@@ -1,37 +1,6 @@
|
|||||||
"""Conversion functions between RGB and other color systems.
|
|
||||||
This modules provides two functions for each color system ABC:
|
|
||||||
rgb_to_abc(r, g, b) --> a, b, c
|
|
||||||
abc_to_rgb(a, b, c) --> r, g, b
|
|
||||||
All inputs and outputs are triples of floats in the range [0.0...1.0]
|
|
||||||
(with the exception of I and Q, which covers a slightly larger range).
|
|
||||||
Inputs outside the valid range may cause exceptions or invalid outputs.
|
|
||||||
Supported color systems:
|
|
||||||
RGB: Red, Green, Blue components
|
|
||||||
YIQ: Luminance, Chrominance (used by composite video signals)
|
|
||||||
HLS: Hue, Luminance, Saturation
|
|
||||||
HSV: Hue, Saturation, Value
|
|
||||||
"""
|
|
||||||
|
|
||||||
# References:
|
# TODO: implement standard library here
|
||||||
# http://en.wikipedia.org/wiki/YIQ
|
|
||||||
# http://en.wikipedia.org/wiki/HLS_color_space
|
|
||||||
# http://en.wikipedia.org/wiki/HSV_color_space
|
|
||||||
|
|
||||||
__all__ = ["rgb_to_yiq","yiq_to_rgb","rgb_to_hls","hls_to_rgb",
|
|
||||||
"rgb_to_hsv","hsv_to_rgb"]
|
|
||||||
|
|
||||||
# Some floating point constants
|
|
||||||
|
|
||||||
ONE_THIRD = 1.0/3.0
|
|
||||||
ONE_SIXTH = 1.0/6.0
|
|
||||||
TWO_THIRD = 2.0/3.0
|
|
||||||
|
|
||||||
# YIQ: used by composite video signals (linear combinations of RGB)
|
|
||||||
# Y: perceived grey level (0.0 == black, 1.0 == white)
|
|
||||||
# I, Q: color components
|
|
||||||
#
|
|
||||||
# There are a great many versions of the constants used in these formulae.
|
|
||||||
# The ones in this library uses constants from the FCC version of NTSC.
|
|
||||||
|
|
||||||
def rgb_to_yiq(r, g, b):
|
def rgb_to_yiq(r, g, b):
|
||||||
y = 0.30*r + 0.59*g + 0.11*b
|
y = 0.30*r + 0.59*g + 0.11*b
|
||||||
@@ -39,122 +8,3 @@ def rgb_to_yiq(r, g, b):
|
|||||||
q = 0.48*(r-y) + 0.41*(b-y)
|
q = 0.48*(r-y) + 0.41*(b-y)
|
||||||
return (y, i, q)
|
return (y, i, q)
|
||||||
|
|
||||||
def yiq_to_rgb(y, i, q):
|
|
||||||
# r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48)
|
|
||||||
# b = y + (0.74*q - 0.48*i) / (0.74*0.41 + 0.27*0.48)
|
|
||||||
# g = y - (0.30*(r-y) + 0.11*(b-y)) / 0.59
|
|
||||||
|
|
||||||
r = y + 0.9468822170900693*i + 0.6235565819861433*q
|
|
||||||
g = y - 0.27478764629897834*i - 0.6356910791873801*q
|
|
||||||
b = y - 1.1085450346420322*i + 1.7090069284064666*q
|
|
||||||
|
|
||||||
if r < 0.0:
|
|
||||||
r = 0.0
|
|
||||||
if g < 0.0:
|
|
||||||
g = 0.0
|
|
||||||
if b < 0.0:
|
|
||||||
b = 0.0
|
|
||||||
if r > 1.0:
|
|
||||||
r = 1.0
|
|
||||||
if g > 1.0:
|
|
||||||
g = 1.0
|
|
||||||
if b > 1.0:
|
|
||||||
b = 1.0
|
|
||||||
return (r, g, b)
|
|
||||||
|
|
||||||
|
|
||||||
# HLS: Hue, Luminance, Saturation
|
|
||||||
# H: position in the spectrum
|
|
||||||
# L: color lightness
|
|
||||||
# S: color saturation
|
|
||||||
|
|
||||||
def rgb_to_hls(r, g, b):
|
|
||||||
maxc = max(r, g, b)
|
|
||||||
minc = min(r, g, b)
|
|
||||||
# XXX Can optimize (maxc+minc) and (maxc-minc)
|
|
||||||
l = (minc+maxc)/2.0
|
|
||||||
if minc == maxc:
|
|
||||||
return 0.0, l, 0.0
|
|
||||||
if l <= 0.5:
|
|
||||||
s = (maxc-minc) / (maxc+minc)
|
|
||||||
else:
|
|
||||||
s = (maxc-minc) / (2.0-maxc-minc)
|
|
||||||
rc = (maxc-r) / (maxc-minc)
|
|
||||||
gc = (maxc-g) / (maxc-minc)
|
|
||||||
bc = (maxc-b) / (maxc-minc)
|
|
||||||
if r == maxc:
|
|
||||||
h = bc-gc
|
|
||||||
elif g == maxc:
|
|
||||||
h = 2.0+rc-bc
|
|
||||||
else:
|
|
||||||
h = 4.0+gc-rc
|
|
||||||
h = (h/6.0) % 1.0
|
|
||||||
return h, l, s
|
|
||||||
|
|
||||||
def hls_to_rgb(h, l, s):
|
|
||||||
if s == 0.0:
|
|
||||||
return l, l, l
|
|
||||||
if l <= 0.5:
|
|
||||||
m2 = l * (1.0+s)
|
|
||||||
else:
|
|
||||||
m2 = l+s-(l*s)
|
|
||||||
m1 = 2.0*l - m2
|
|
||||||
return (_v(m1, m2, h+ONE_THIRD), _v(m1, m2, h), _v(m1, m2, h-ONE_THIRD))
|
|
||||||
|
|
||||||
def _v(m1, m2, hue):
|
|
||||||
hue = hue % 1.0
|
|
||||||
if hue < ONE_SIXTH:
|
|
||||||
return m1 + (m2-m1)*hue*6.0
|
|
||||||
if hue < 0.5:
|
|
||||||
return m2
|
|
||||||
if hue < TWO_THIRD:
|
|
||||||
return m1 + (m2-m1)*(TWO_THIRD-hue)*6.0
|
|
||||||
return m1
|
|
||||||
|
|
||||||
|
|
||||||
# HSV: Hue, Saturation, Value
|
|
||||||
# H: position in the spectrum
|
|
||||||
# S: color saturation ("purity")
|
|
||||||
# V: color brightness
|
|
||||||
|
|
||||||
def rgb_to_hsv(r, g, b):
|
|
||||||
maxc = max(r, g, b)
|
|
||||||
minc = min(r, g, b)
|
|
||||||
v = maxc
|
|
||||||
if minc == maxc:
|
|
||||||
return 0.0, 0.0, v
|
|
||||||
s = (maxc-minc) / maxc
|
|
||||||
rc = (maxc-r) / (maxc-minc)
|
|
||||||
gc = (maxc-g) / (maxc-minc)
|
|
||||||
bc = (maxc-b) / (maxc-minc)
|
|
||||||
if r == maxc:
|
|
||||||
h = bc-gc
|
|
||||||
elif g == maxc:
|
|
||||||
h = 2.0+rc-bc
|
|
||||||
else:
|
|
||||||
h = 4.0+gc-rc
|
|
||||||
h = (h/6.0) % 1.0
|
|
||||||
return h, s, v
|
|
||||||
|
|
||||||
def hsv_to_rgb(h, s, v):
|
|
||||||
if s == 0.0:
|
|
||||||
return v, v, v
|
|
||||||
i = int(h*6.0) # XXX assume int() truncates!
|
|
||||||
f = (h*6.0) - i
|
|
||||||
p = v*(1.0 - s)
|
|
||||||
q = v*(1.0 - s*f)
|
|
||||||
t = v*(1.0 - s*(1.0-f))
|
|
||||||
i = i%6
|
|
||||||
if i == 0:
|
|
||||||
return v, t, p
|
|
||||||
if i == 1:
|
|
||||||
return q, v, p
|
|
||||||
if i == 2:
|
|
||||||
return p, v, t
|
|
||||||
if i == 3:
|
|
||||||
return p, q, v
|
|
||||||
if i == 4:
|
|
||||||
return t, p, v
|
|
||||||
if i == 5:
|
|
||||||
return v, p, q
|
|
||||||
# Cannot get here
|
|
||||||
|
|||||||
@@ -1,295 +0,0 @@
|
|||||||
"""Module/script to byte-compile all .py files to .pyc files.
|
|
||||||
|
|
||||||
When called as a script with arguments, this compiles the directories
|
|
||||||
given as arguments recursively; the -l option prevents it from
|
|
||||||
recursing into directories.
|
|
||||||
|
|
||||||
Without arguments, if compiles all modules on sys.path, without
|
|
||||||
recursing into subdirectories. (Even though it should do so for
|
|
||||||
packages -- for now, you'll have to deal with packages separately.)
|
|
||||||
|
|
||||||
See module py_compile for details of the actual byte-compilation.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import importlib.util
|
|
||||||
import py_compile
|
|
||||||
import struct
|
|
||||||
|
|
||||||
try:
|
|
||||||
from concurrent.futures import ProcessPoolExecutor
|
|
||||||
except ImportError:
|
|
||||||
ProcessPoolExecutor = None
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
__all__ = ["compile_dir","compile_file","compile_path"]
|
|
||||||
|
|
||||||
def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0):
|
|
||||||
if quiet < 2 and isinstance(dir, os.PathLike):
|
|
||||||
dir = os.fspath(dir)
|
|
||||||
if not quiet:
|
|
||||||
print('Listing {!r}...'.format(dir))
|
|
||||||
try:
|
|
||||||
names = os.listdir(dir)
|
|
||||||
except OSError:
|
|
||||||
if quiet < 2:
|
|
||||||
print("Can't list {!r}".format(dir))
|
|
||||||
names = []
|
|
||||||
names.sort()
|
|
||||||
for name in names:
|
|
||||||
if name == '__pycache__':
|
|
||||||
continue
|
|
||||||
fullname = os.path.join(dir, name)
|
|
||||||
if ddir is not None:
|
|
||||||
dfile = os.path.join(ddir, name)
|
|
||||||
else:
|
|
||||||
dfile = None
|
|
||||||
if not os.path.isdir(fullname):
|
|
||||||
yield fullname
|
|
||||||
elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
|
|
||||||
os.path.isdir(fullname) and not os.path.islink(fullname)):
|
|
||||||
yield from _walk_dir(fullname, ddir=dfile,
|
|
||||||
maxlevels=maxlevels - 1, quiet=quiet)
|
|
||||||
|
|
||||||
def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
|
|
||||||
quiet=0, legacy=False, optimize=-1, workers=1):
|
|
||||||
"""Byte-compile all modules in the given directory tree.
|
|
||||||
|
|
||||||
Arguments (only dir is required):
|
|
||||||
|
|
||||||
dir: the directory to byte-compile
|
|
||||||
maxlevels: maximum recursion level (default 10)
|
|
||||||
ddir: the directory that will be prepended to the path to the
|
|
||||||
file as it is compiled into each byte-code file.
|
|
||||||
force: if True, force compilation, even if timestamps are up-to-date
|
|
||||||
quiet: full output with False or 0, errors only with 1,
|
|
||||||
no output with 2
|
|
||||||
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
|
||||||
optimize: optimization level or -1 for level of the interpreter
|
|
||||||
workers: maximum number of parallel workers
|
|
||||||
"""
|
|
||||||
if workers is not None and workers < 0:
|
|
||||||
raise ValueError('workers must be greater or equal to 0')
|
|
||||||
|
|
||||||
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
|
|
||||||
ddir=ddir)
|
|
||||||
success = True
|
|
||||||
if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
|
|
||||||
workers = workers or None
|
|
||||||
with ProcessPoolExecutor(max_workers=workers) as executor:
|
|
||||||
results = executor.map(partial(compile_file,
|
|
||||||
ddir=ddir, force=force,
|
|
||||||
rx=rx, quiet=quiet,
|
|
||||||
legacy=legacy,
|
|
||||||
optimize=optimize),
|
|
||||||
files)
|
|
||||||
success = min(results, default=True)
|
|
||||||
else:
|
|
||||||
for file in files:
|
|
||||||
if not compile_file(file, ddir, force, rx, quiet,
|
|
||||||
legacy, optimize):
|
|
||||||
success = False
|
|
||||||
return success
|
|
||||||
|
|
||||||
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
|
|
||||||
legacy=False, optimize=-1):
|
|
||||||
"""Byte-compile one file.
|
|
||||||
|
|
||||||
Arguments (only fullname is required):
|
|
||||||
|
|
||||||
fullname: the file to byte-compile
|
|
||||||
ddir: if given, the directory name compiled in to the
|
|
||||||
byte-code file.
|
|
||||||
force: if True, force compilation, even if timestamps are up-to-date
|
|
||||||
quiet: full output with False or 0, errors only with 1,
|
|
||||||
no output with 2
|
|
||||||
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
|
|
||||||
optimize: optimization level or -1 for level of the interpreter
|
|
||||||
"""
|
|
||||||
success = True
|
|
||||||
if quiet < 2 and isinstance(fullname, os.PathLike):
|
|
||||||
fullname = os.fspath(fullname)
|
|
||||||
name = os.path.basename(fullname)
|
|
||||||
if ddir is not None:
|
|
||||||
dfile = os.path.join(ddir, name)
|
|
||||||
else:
|
|
||||||
dfile = None
|
|
||||||
if rx is not None:
|
|
||||||
mo = rx.search(fullname)
|
|
||||||
if mo:
|
|
||||||
return success
|
|
||||||
if os.path.isfile(fullname):
|
|
||||||
if legacy:
|
|
||||||
cfile = fullname + 'c'
|
|
||||||
else:
|
|
||||||
if optimize >= 0:
|
|
||||||
opt = optimize if optimize >= 1 else ''
|
|
||||||
cfile = importlib.util.cache_from_source(
|
|
||||||
fullname, optimization=opt)
|
|
||||||
else:
|
|
||||||
cfile = importlib.util.cache_from_source(fullname)
|
|
||||||
cache_dir = os.path.dirname(cfile)
|
|
||||||
head, tail = name[:-3], name[-3:]
|
|
||||||
if tail == '.py':
|
|
||||||
if not force:
|
|
||||||
try:
|
|
||||||
mtime = int(os.stat(fullname).st_mtime)
|
|
||||||
expect = struct.pack('<4sl', importlib.util.MAGIC_NUMBER,
|
|
||||||
mtime)
|
|
||||||
with open(cfile, 'rb') as chandle:
|
|
||||||
actual = chandle.read(8)
|
|
||||||
if expect == actual:
|
|
||||||
return success
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
if not quiet:
|
|
||||||
print('Compiling {!r}...'.format(fullname))
|
|
||||||
try:
|
|
||||||
ok = py_compile.compile(fullname, cfile, dfile, True,
|
|
||||||
optimize=optimize)
|
|
||||||
except py_compile.PyCompileError as err:
|
|
||||||
success = False
|
|
||||||
if quiet >= 2:
|
|
||||||
return success
|
|
||||||
elif quiet:
|
|
||||||
print('*** Error compiling {!r}...'.format(fullname))
|
|
||||||
else:
|
|
||||||
print('*** ', end='')
|
|
||||||
# escape non-printable characters in msg
|
|
||||||
msg = err.msg.encode(sys.stdout.encoding,
|
|
||||||
errors='backslashreplace')
|
|
||||||
msg = msg.decode(sys.stdout.encoding)
|
|
||||||
print(msg)
|
|
||||||
except (SyntaxError, UnicodeError, OSError) as e:
|
|
||||||
success = False
|
|
||||||
if quiet >= 2:
|
|
||||||
return success
|
|
||||||
elif quiet:
|
|
||||||
print('*** Error compiling {!r}...'.format(fullname))
|
|
||||||
else:
|
|
||||||
print('*** ', end='')
|
|
||||||
print(e.__class__.__name__ + ':', e)
|
|
||||||
else:
|
|
||||||
if ok == 0:
|
|
||||||
success = False
|
|
||||||
return success
|
|
||||||
|
|
||||||
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
|
|
||||||
legacy=False, optimize=-1):
|
|
||||||
"""Byte-compile all module on sys.path.
|
|
||||||
|
|
||||||
Arguments (all optional):
|
|
||||||
|
|
||||||
skip_curdir: if true, skip current directory (default True)
|
|
||||||
maxlevels: max recursion level (default 0)
|
|
||||||
force: as for compile_dir() (default False)
|
|
||||||
quiet: as for compile_dir() (default 0)
|
|
||||||
legacy: as for compile_dir() (default False)
|
|
||||||
optimize: as for compile_dir() (default -1)
|
|
||||||
"""
|
|
||||||
success = True
|
|
||||||
for dir in sys.path:
|
|
||||||
if (not dir or dir == os.curdir) and skip_curdir:
|
|
||||||
if quiet < 2:
|
|
||||||
print('Skipping current directory')
|
|
||||||
else:
|
|
||||||
success = success and compile_dir(dir, maxlevels, None,
|
|
||||||
force, quiet=quiet,
|
|
||||||
legacy=legacy, optimize=optimize)
|
|
||||||
return success
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Script main program."""
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Utilities to support installing Python libraries.')
|
|
||||||
parser.add_argument('-l', action='store_const', const=0,
|
|
||||||
default=10, dest='maxlevels',
|
|
||||||
help="don't recurse into subdirectories")
|
|
||||||
parser.add_argument('-r', type=int, dest='recursion',
|
|
||||||
help=('control the maximum recursion level. '
|
|
||||||
'if `-l` and `-r` options are specified, '
|
|
||||||
'then `-r` takes precedence.'))
|
|
||||||
parser.add_argument('-f', action='store_true', dest='force',
|
|
||||||
help='force rebuild even if timestamps are up to date')
|
|
||||||
parser.add_argument('-q', action='count', dest='quiet', default=0,
|
|
||||||
help='output only error messages; -qq will suppress '
|
|
||||||
'the error messages as well.')
|
|
||||||
parser.add_argument('-b', action='store_true', dest='legacy',
|
|
||||||
help='use legacy (pre-PEP3147) compiled file locations')
|
|
||||||
parser.add_argument('-d', metavar='DESTDIR', dest='ddir', default=None,
|
|
||||||
help=('directory to prepend to file paths for use in '
|
|
||||||
'compile-time tracebacks and in runtime '
|
|
||||||
'tracebacks in cases where the source file is '
|
|
||||||
'unavailable'))
|
|
||||||
parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
|
|
||||||
help=('skip files matching the regular expression; '
|
|
||||||
'the regexp is searched for in the full path '
|
|
||||||
'of each file considered for compilation'))
|
|
||||||
parser.add_argument('-i', metavar='FILE', dest='flist',
|
|
||||||
help=('add all the files and directories listed in '
|
|
||||||
'FILE to the list considered for compilation; '
|
|
||||||
'if "-", names are read from stdin'))
|
|
||||||
parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*',
|
|
||||||
help=('zero or more file and directory names '
|
|
||||||
'to compile; if no arguments given, defaults '
|
|
||||||
'to the equivalent of -l sys.path'))
|
|
||||||
parser.add_argument('-j', '--workers', default=1,
|
|
||||||
type=int, help='Run compileall concurrently')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
compile_dests = args.compile_dest
|
|
||||||
|
|
||||||
if args.rx:
|
|
||||||
import re
|
|
||||||
args.rx = re.compile(args.rx)
|
|
||||||
|
|
||||||
|
|
||||||
if args.recursion is not None:
|
|
||||||
maxlevels = args.recursion
|
|
||||||
else:
|
|
||||||
maxlevels = args.maxlevels
|
|
||||||
|
|
||||||
# if flist is provided then load it
|
|
||||||
if args.flist:
|
|
||||||
try:
|
|
||||||
with (sys.stdin if args.flist=='-' else open(args.flist)) as f:
|
|
||||||
for line in f:
|
|
||||||
compile_dests.append(line.strip())
|
|
||||||
except OSError:
|
|
||||||
if args.quiet < 2:
|
|
||||||
print("Error reading file list {}".format(args.flist))
|
|
||||||
return False
|
|
||||||
|
|
||||||
if args.workers is not None:
|
|
||||||
args.workers = args.workers or None
|
|
||||||
|
|
||||||
success = True
|
|
||||||
try:
|
|
||||||
if compile_dests:
|
|
||||||
for dest in compile_dests:
|
|
||||||
if os.path.isfile(dest):
|
|
||||||
if not compile_file(dest, args.ddir, args.force, args.rx,
|
|
||||||
args.quiet, args.legacy):
|
|
||||||
success = False
|
|
||||||
else:
|
|
||||||
if not compile_dir(dest, maxlevels, args.ddir,
|
|
||||||
args.force, args.rx, args.quiet,
|
|
||||||
args.legacy, workers=args.workers):
|
|
||||||
success = False
|
|
||||||
return success
|
|
||||||
else:
|
|
||||||
return compile_path(legacy=args.legacy, force=args.force,
|
|
||||||
quiet=args.quiet)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
if args.quiet < 2:
|
|
||||||
print("\n[interrupted]")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
exit_status = int(not main())
|
|
||||||
sys.exit(exit_status)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# This directory is a Python package.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Copyright 2009 Brian Quinlan. All Rights Reserved.
|
|
||||||
# Licensed to PSF under a Contributor Agreement.
|
|
||||||
|
|
||||||
"""Execute computations asynchronously using threads or processes."""
|
|
||||||
|
|
||||||
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
|
|
||||||
|
|
||||||
from concurrent.futures._base import (FIRST_COMPLETED,
|
|
||||||
FIRST_EXCEPTION,
|
|
||||||
ALL_COMPLETED,
|
|
||||||
CancelledError,
|
|
||||||
TimeoutError,
|
|
||||||
Future,
|
|
||||||
Executor,
|
|
||||||
wait,
|
|
||||||
as_completed)
|
|
||||||
from concurrent.futures.process import ProcessPoolExecutor
|
|
||||||
from concurrent.futures.thread import ThreadPoolExecutor
|
|
||||||
@@ -1,582 +0,0 @@
|
|||||||
# Copyright 2009 Brian Quinlan. All Rights Reserved.
|
|
||||||
# Licensed to PSF under a Contributor Agreement.
|
|
||||||
|
|
||||||
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
FIRST_COMPLETED = 'FIRST_COMPLETED'
|
|
||||||
FIRST_EXCEPTION = 'FIRST_EXCEPTION'
|
|
||||||
ALL_COMPLETED = 'ALL_COMPLETED'
|
|
||||||
_AS_COMPLETED = '_AS_COMPLETED'
|
|
||||||
|
|
||||||
# Possible future states (for internal use by the futures package).
|
|
||||||
PENDING = 'PENDING'
|
|
||||||
RUNNING = 'RUNNING'
|
|
||||||
# The future was cancelled by the user...
|
|
||||||
CANCELLED = 'CANCELLED'
|
|
||||||
# ...and _Waiter.add_cancelled() was called by a worker.
|
|
||||||
CANCELLED_AND_NOTIFIED = 'CANCELLED_AND_NOTIFIED'
|
|
||||||
FINISHED = 'FINISHED'
|
|
||||||
|
|
||||||
_FUTURE_STATES = [
|
|
||||||
PENDING,
|
|
||||||
RUNNING,
|
|
||||||
CANCELLED,
|
|
||||||
CANCELLED_AND_NOTIFIED,
|
|
||||||
FINISHED
|
|
||||||
]
|
|
||||||
|
|
||||||
_STATE_TO_DESCRIPTION_MAP = {
|
|
||||||
PENDING: "pending",
|
|
||||||
RUNNING: "running",
|
|
||||||
CANCELLED: "cancelled",
|
|
||||||
CANCELLED_AND_NOTIFIED: "cancelled",
|
|
||||||
FINISHED: "finished"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Logger for internal use by the futures package.
|
|
||||||
LOGGER = logging.getLogger("concurrent.futures")
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
"""Base class for all future-related exceptions."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class CancelledError(Error):
|
|
||||||
"""The Future was cancelled."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class TimeoutError(Error):
|
|
||||||
"""The operation exceeded the given deadline."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class _Waiter(object):
|
|
||||||
"""Provides the event that wait() and as_completed() block on."""
|
|
||||||
def __init__(self):
|
|
||||||
self.event = threading.Event()
|
|
||||||
self.finished_futures = []
|
|
||||||
|
|
||||||
def add_result(self, future):
|
|
||||||
self.finished_futures.append(future)
|
|
||||||
|
|
||||||
def add_exception(self, future):
|
|
||||||
self.finished_futures.append(future)
|
|
||||||
|
|
||||||
def add_cancelled(self, future):
|
|
||||||
self.finished_futures.append(future)
|
|
||||||
|
|
||||||
class _AsCompletedWaiter(_Waiter):
|
|
||||||
"""Used by as_completed()."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(_AsCompletedWaiter, self).__init__()
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
|
|
||||||
def add_result(self, future):
|
|
||||||
with self.lock:
|
|
||||||
super(_AsCompletedWaiter, self).add_result(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
def add_exception(self, future):
|
|
||||||
with self.lock:
|
|
||||||
super(_AsCompletedWaiter, self).add_exception(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
def add_cancelled(self, future):
|
|
||||||
with self.lock:
|
|
||||||
super(_AsCompletedWaiter, self).add_cancelled(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
class _FirstCompletedWaiter(_Waiter):
|
|
||||||
"""Used by wait(return_when=FIRST_COMPLETED)."""
|
|
||||||
|
|
||||||
def add_result(self, future):
|
|
||||||
super().add_result(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
def add_exception(self, future):
|
|
||||||
super().add_exception(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
def add_cancelled(self, future):
|
|
||||||
super().add_cancelled(future)
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
class _AllCompletedWaiter(_Waiter):
|
|
||||||
"""Used by wait(return_when=FIRST_EXCEPTION and ALL_COMPLETED)."""
|
|
||||||
|
|
||||||
def __init__(self, num_pending_calls, stop_on_exception):
|
|
||||||
self.num_pending_calls = num_pending_calls
|
|
||||||
self.stop_on_exception = stop_on_exception
|
|
||||||
self.lock = threading.Lock()
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def _decrement_pending_calls(self):
|
|
||||||
with self.lock:
|
|
||||||
self.num_pending_calls -= 1
|
|
||||||
if not self.num_pending_calls:
|
|
||||||
self.event.set()
|
|
||||||
|
|
||||||
def add_result(self, future):
|
|
||||||
super().add_result(future)
|
|
||||||
self._decrement_pending_calls()
|
|
||||||
|
|
||||||
def add_exception(self, future):
|
|
||||||
super().add_exception(future)
|
|
||||||
if self.stop_on_exception:
|
|
||||||
self.event.set()
|
|
||||||
else:
|
|
||||||
self._decrement_pending_calls()
|
|
||||||
|
|
||||||
def add_cancelled(self, future):
|
|
||||||
super().add_cancelled(future)
|
|
||||||
self._decrement_pending_calls()
|
|
||||||
|
|
||||||
class _AcquireFutures(object):
|
|
||||||
"""A context manager that does an ordered acquire of Future conditions."""
|
|
||||||
|
|
||||||
def __init__(self, futures):
|
|
||||||
self.futures = sorted(futures, key=id)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
for future in self.futures:
|
|
||||||
future._condition.acquire()
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
for future in self.futures:
|
|
||||||
future._condition.release()
|
|
||||||
|
|
||||||
def _create_and_install_waiters(fs, return_when):
|
|
||||||
if return_when == _AS_COMPLETED:
|
|
||||||
waiter = _AsCompletedWaiter()
|
|
||||||
elif return_when == FIRST_COMPLETED:
|
|
||||||
waiter = _FirstCompletedWaiter()
|
|
||||||
else:
|
|
||||||
pending_count = sum(
|
|
||||||
f._state not in [CANCELLED_AND_NOTIFIED, FINISHED] for f in fs)
|
|
||||||
|
|
||||||
if return_when == FIRST_EXCEPTION:
|
|
||||||
waiter = _AllCompletedWaiter(pending_count, stop_on_exception=True)
|
|
||||||
elif return_when == ALL_COMPLETED:
|
|
||||||
waiter = _AllCompletedWaiter(pending_count, stop_on_exception=False)
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid return condition: %r" % return_when)
|
|
||||||
|
|
||||||
for f in fs:
|
|
||||||
f._waiters.append(waiter)
|
|
||||||
|
|
||||||
return waiter
|
|
||||||
|
|
||||||
def as_completed(fs, timeout=None):
|
|
||||||
"""An iterator over the given futures that yields each as it completes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: The sequence of Futures (possibly created by different Executors) to
|
|
||||||
iterate over.
|
|
||||||
timeout: The maximum number of seconds to wait. If None, then there
|
|
||||||
is no limit on the wait time.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An iterator that yields the given Futures as they complete (finished or
|
|
||||||
cancelled). If any given Futures are duplicated, they will be returned
|
|
||||||
once.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TimeoutError: If the entire result iterator could not be generated
|
|
||||||
before the given timeout.
|
|
||||||
"""
|
|
||||||
if timeout is not None:
|
|
||||||
end_time = timeout + time.time()
|
|
||||||
|
|
||||||
fs = set(fs)
|
|
||||||
with _AcquireFutures(fs):
|
|
||||||
finished = set(
|
|
||||||
f for f in fs
|
|
||||||
if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
|
|
||||||
pending = fs - finished
|
|
||||||
waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield from finished
|
|
||||||
|
|
||||||
while pending:
|
|
||||||
if timeout is None:
|
|
||||||
wait_timeout = None
|
|
||||||
else:
|
|
||||||
wait_timeout = end_time - time.time()
|
|
||||||
if wait_timeout < 0:
|
|
||||||
raise TimeoutError(
|
|
||||||
'%d (of %d) futures unfinished' % (
|
|
||||||
len(pending), len(fs)))
|
|
||||||
|
|
||||||
waiter.event.wait(wait_timeout)
|
|
||||||
|
|
||||||
with waiter.lock:
|
|
||||||
finished = waiter.finished_futures
|
|
||||||
waiter.finished_futures = []
|
|
||||||
waiter.event.clear()
|
|
||||||
|
|
||||||
for future in finished:
|
|
||||||
yield future
|
|
||||||
pending.remove(future)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
for f in fs:
|
|
||||||
with f._condition:
|
|
||||||
f._waiters.remove(waiter)
|
|
||||||
|
|
||||||
DoneAndNotDoneFutures = collections.namedtuple(
|
|
||||||
'DoneAndNotDoneFutures', 'done not_done')
|
|
||||||
def wait(fs, timeout=None, return_when=ALL_COMPLETED):
|
|
||||||
"""Wait for the futures in the given sequence to complete.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: The sequence of Futures (possibly created by different Executors) to
|
|
||||||
wait upon.
|
|
||||||
timeout: The maximum number of seconds to wait. If None, then there
|
|
||||||
is no limit on the wait time.
|
|
||||||
return_when: Indicates when this function should return. The options
|
|
||||||
are:
|
|
||||||
|
|
||||||
FIRST_COMPLETED - Return when any future finishes or is
|
|
||||||
cancelled.
|
|
||||||
FIRST_EXCEPTION - Return when any future finishes by raising an
|
|
||||||
exception. If no future raises an exception
|
|
||||||
then it is equivalent to ALL_COMPLETED.
|
|
||||||
ALL_COMPLETED - Return when all futures finish or are cancelled.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A named 2-tuple of sets. The first set, named 'done', contains the
|
|
||||||
futures that completed (is finished or cancelled) before the wait
|
|
||||||
completed. The second set, named 'not_done', contains uncompleted
|
|
||||||
futures.
|
|
||||||
"""
|
|
||||||
with _AcquireFutures(fs):
|
|
||||||
done = set(f for f in fs
|
|
||||||
if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
|
|
||||||
not_done = set(fs) - done
|
|
||||||
|
|
||||||
if (return_when == FIRST_COMPLETED) and done:
|
|
||||||
return DoneAndNotDoneFutures(done, not_done)
|
|
||||||
elif (return_when == FIRST_EXCEPTION) and done:
|
|
||||||
if any(f for f in done
|
|
||||||
if not f.cancelled() and f.exception() is not None):
|
|
||||||
return DoneAndNotDoneFutures(done, not_done)
|
|
||||||
|
|
||||||
if len(done) == len(fs):
|
|
||||||
return DoneAndNotDoneFutures(done, not_done)
|
|
||||||
|
|
||||||
waiter = _create_and_install_waiters(fs, return_when)
|
|
||||||
|
|
||||||
waiter.event.wait(timeout)
|
|
||||||
for f in fs:
|
|
||||||
with f._condition:
|
|
||||||
f._waiters.remove(waiter)
|
|
||||||
|
|
||||||
done.update(waiter.finished_futures)
|
|
||||||
return DoneAndNotDoneFutures(done, set(fs) - done)
|
|
||||||
|
|
||||||
class Future(object):
|
|
||||||
"""Represents the result of an asynchronous computation."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initializes the future. Should not be called by clients."""
|
|
||||||
self._condition = threading.Condition()
|
|
||||||
self._state = PENDING
|
|
||||||
self._result = None
|
|
||||||
self._exception = None
|
|
||||||
self._waiters = []
|
|
||||||
self._done_callbacks = []
|
|
||||||
|
|
||||||
def _invoke_callbacks(self):
|
|
||||||
for callback in self._done_callbacks:
|
|
||||||
try:
|
|
||||||
callback(self)
|
|
||||||
except Exception:
|
|
||||||
LOGGER.exception('exception calling callback for %r', self)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
with self._condition:
|
|
||||||
if self._state == FINISHED:
|
|
||||||
if self._exception:
|
|
||||||
return '<%s at %#x state=%s raised %s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
id(self),
|
|
||||||
_STATE_TO_DESCRIPTION_MAP[self._state],
|
|
||||||
self._exception.__class__.__name__)
|
|
||||||
else:
|
|
||||||
return '<%s at %#x state=%s returned %s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
id(self),
|
|
||||||
_STATE_TO_DESCRIPTION_MAP[self._state],
|
|
||||||
self._result.__class__.__name__)
|
|
||||||
return '<%s at %#x state=%s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
id(self),
|
|
||||||
_STATE_TO_DESCRIPTION_MAP[self._state])
|
|
||||||
|
|
||||||
def cancel(self):
|
|
||||||
"""Cancel the future if possible.
|
|
||||||
|
|
||||||
Returns True if the future was cancelled, False otherwise. A future
|
|
||||||
cannot be cancelled if it is running or has already completed.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
if self._state in [RUNNING, FINISHED]:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
|
||||||
return True
|
|
||||||
|
|
||||||
self._state = CANCELLED
|
|
||||||
self._condition.notify_all()
|
|
||||||
|
|
||||||
self._invoke_callbacks()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def cancelled(self):
|
|
||||||
"""Return True if the future was cancelled."""
|
|
||||||
with self._condition:
|
|
||||||
return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]
|
|
||||||
|
|
||||||
def running(self):
|
|
||||||
"""Return True if the future is currently executing."""
|
|
||||||
with self._condition:
|
|
||||||
return self._state == RUNNING
|
|
||||||
|
|
||||||
def done(self):
|
|
||||||
"""Return True of the future was cancelled or finished executing."""
|
|
||||||
with self._condition:
|
|
||||||
return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
|
|
||||||
|
|
||||||
def __get_result(self):
|
|
||||||
if self._exception:
|
|
||||||
raise self._exception
|
|
||||||
else:
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
def add_done_callback(self, fn):
|
|
||||||
"""Attaches a callable that will be called when the future finishes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fn: A callable that will be called with this future as its only
|
|
||||||
argument when the future completes or is cancelled. The callable
|
|
||||||
will always be called by a thread in the same process in which
|
|
||||||
it was added. If the future has already completed or been
|
|
||||||
cancelled then the callable will be called immediately. These
|
|
||||||
callables are called in the order that they were added.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
|
|
||||||
self._done_callbacks.append(fn)
|
|
||||||
return
|
|
||||||
fn(self)
|
|
||||||
|
|
||||||
def result(self, timeout=None):
|
|
||||||
"""Return the result of the call that the future represents.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: The number of seconds to wait for the result if the future
|
|
||||||
isn't done. If None, then there is no limit on the wait time.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result of the call that the future represents.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
CancelledError: If the future was cancelled.
|
|
||||||
TimeoutError: If the future didn't finish executing before the given
|
|
||||||
timeout.
|
|
||||||
Exception: If the call raised then that exception will be raised.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
|
||||||
raise CancelledError()
|
|
||||||
elif self._state == FINISHED:
|
|
||||||
return self.__get_result()
|
|
||||||
|
|
||||||
self._condition.wait(timeout)
|
|
||||||
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
|
||||||
raise CancelledError()
|
|
||||||
elif self._state == FINISHED:
|
|
||||||
return self.__get_result()
|
|
||||||
else:
|
|
||||||
raise TimeoutError()
|
|
||||||
|
|
||||||
def exception(self, timeout=None):
|
|
||||||
"""Return the exception raised by the call that the future represents.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: The number of seconds to wait for the exception if the
|
|
||||||
future isn't done. If None, then there is no limit on the wait
|
|
||||||
time.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The exception raised by the call that the future represents or None
|
|
||||||
if the call completed without raising.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
CancelledError: If the future was cancelled.
|
|
||||||
TimeoutError: If the future didn't finish executing before the given
|
|
||||||
timeout.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with self._condition:
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
|
||||||
raise CancelledError()
|
|
||||||
elif self._state == FINISHED:
|
|
||||||
return self._exception
|
|
||||||
|
|
||||||
self._condition.wait(timeout)
|
|
||||||
|
|
||||||
if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
|
|
||||||
raise CancelledError()
|
|
||||||
elif self._state == FINISHED:
|
|
||||||
return self._exception
|
|
||||||
else:
|
|
||||||
raise TimeoutError()
|
|
||||||
|
|
||||||
# The following methods should only be used by Executors and in tests.
|
|
||||||
def set_running_or_notify_cancel(self):
|
|
||||||
"""Mark the future as running or process any cancel notifications.
|
|
||||||
|
|
||||||
Should only be used by Executor implementations and unit tests.
|
|
||||||
|
|
||||||
If the future has been cancelled (cancel() was called and returned
|
|
||||||
True) then any threads waiting on the future completing (though calls
|
|
||||||
to as_completed() or wait()) are notified and False is returned.
|
|
||||||
|
|
||||||
If the future was not cancelled then it is put in the running state
|
|
||||||
(future calls to running() will return True) and True is returned.
|
|
||||||
|
|
||||||
This method should be called by Executor implementations before
|
|
||||||
executing the work associated with this future. If this method returns
|
|
||||||
False then the work should not be executed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
False if the Future was cancelled, True otherwise.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
RuntimeError: if this method was already called or if set_result()
|
|
||||||
or set_exception() was called.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
if self._state == CANCELLED:
|
|
||||||
self._state = CANCELLED_AND_NOTIFIED
|
|
||||||
for waiter in self._waiters:
|
|
||||||
waiter.add_cancelled(self)
|
|
||||||
# self._condition.notify_all() is not necessary because
|
|
||||||
# self.cancel() triggers a notification.
|
|
||||||
return False
|
|
||||||
elif self._state == PENDING:
|
|
||||||
self._state = RUNNING
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
LOGGER.critical('Future %s in unexpected state: %s',
|
|
||||||
id(self),
|
|
||||||
self._state)
|
|
||||||
raise RuntimeError('Future in unexpected state')
|
|
||||||
|
|
||||||
def set_result(self, result):
|
|
||||||
"""Sets the return value of work associated with the future.
|
|
||||||
|
|
||||||
Should only be used by Executor implementations and unit tests.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
self._result = result
|
|
||||||
self._state = FINISHED
|
|
||||||
for waiter in self._waiters:
|
|
||||||
waiter.add_result(self)
|
|
||||||
self._condition.notify_all()
|
|
||||||
self._invoke_callbacks()
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
"""Sets the result of the future as being the given exception.
|
|
||||||
|
|
||||||
Should only be used by Executor implementations and unit tests.
|
|
||||||
"""
|
|
||||||
with self._condition:
|
|
||||||
self._exception = exception
|
|
||||||
self._state = FINISHED
|
|
||||||
for waiter in self._waiters:
|
|
||||||
waiter.add_exception(self)
|
|
||||||
self._condition.notify_all()
|
|
||||||
self._invoke_callbacks()
|
|
||||||
|
|
||||||
class Executor(object):
|
|
||||||
"""This is an abstract base class for concrete asynchronous executors."""
|
|
||||||
|
|
||||||
def submit(self, fn, *args, **kwargs):
|
|
||||||
"""Submits a callable to be executed with the given arguments.
|
|
||||||
|
|
||||||
Schedules the callable to be executed as fn(*args, **kwargs) and returns
|
|
||||||
a Future instance representing the execution of the callable.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A Future representing the given call.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def map(self, fn, *iterables, timeout=None, chunksize=1):
|
|
||||||
"""Returns an iterator equivalent to map(fn, iter).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fn: A callable that will take as many arguments as there are
|
|
||||||
passed iterables.
|
|
||||||
timeout: The maximum number of seconds to wait. If None, then there
|
|
||||||
is no limit on the wait time.
|
|
||||||
chunksize: The size of the chunks the iterable will be broken into
|
|
||||||
before being passed to a child process. This argument is only
|
|
||||||
used by ProcessPoolExecutor; it is ignored by
|
|
||||||
ThreadPoolExecutor.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An iterator equivalent to: map(func, *iterables) but the calls may
|
|
||||||
be evaluated out-of-order.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TimeoutError: If the entire result iterator could not be generated
|
|
||||||
before the given timeout.
|
|
||||||
Exception: If fn(*args) raises for any values.
|
|
||||||
"""
|
|
||||||
if timeout is not None:
|
|
||||||
end_time = timeout + time.time()
|
|
||||||
|
|
||||||
fs = [self.submit(fn, *args) for args in zip(*iterables)]
|
|
||||||
|
|
||||||
# Yield must be hidden in closure so that the futures are submitted
|
|
||||||
# before the first iterator value is required.
|
|
||||||
def result_iterator():
|
|
||||||
try:
|
|
||||||
for future in fs:
|
|
||||||
if timeout is None:
|
|
||||||
yield future.result()
|
|
||||||
else:
|
|
||||||
yield future.result(end_time - time.time())
|
|
||||||
finally:
|
|
||||||
for future in fs:
|
|
||||||
future.cancel()
|
|
||||||
return result_iterator()
|
|
||||||
|
|
||||||
def shutdown(self, wait=True):
|
|
||||||
"""Clean-up the resources associated with the Executor.
|
|
||||||
|
|
||||||
It is safe to call this method several times. Otherwise, no other
|
|
||||||
methods can be called after this one.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
wait: If True then shutdown will not return until all running
|
|
||||||
futures have finished executing and the resources used by the
|
|
||||||
executor have been reclaimed.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self.shutdown(wait=True)
|
|
||||||
return False
|
|
||||||
@@ -1,503 +0,0 @@
|
|||||||
# Copyright 2009 Brian Quinlan. All Rights Reserved.
|
|
||||||
# Licensed to PSF under a Contributor Agreement.
|
|
||||||
|
|
||||||
"""Implements ProcessPoolExecutor.
|
|
||||||
|
|
||||||
The follow diagram and text describe the data-flow through the system:
|
|
||||||
|
|
||||||
|======================= In-process =====================|== Out-of-process ==|
|
|
||||||
|
|
||||||
+----------+ +----------+ +--------+ +-----------+ +---------+
|
|
||||||
| | => | Work Ids | => | | => | Call Q | => | |
|
|
||||||
| | +----------+ | | +-----------+ | |
|
|
||||||
| | | ... | | | | ... | | |
|
|
||||||
| | | 6 | | | | 5, call() | | |
|
|
||||||
| | | 7 | | | | ... | | |
|
|
||||||
| Process | | ... | | Local | +-----------+ | Process |
|
|
||||||
| Pool | +----------+ | Worker | | #1..n |
|
|
||||||
| Executor | | Thread | | |
|
|
||||||
| | +----------- + | | +-----------+ | |
|
|
||||||
| | <=> | Work Items | <=> | | <= | Result Q | <= | |
|
|
||||||
| | +------------+ | | +-----------+ | |
|
|
||||||
| | | 6: call() | | | | ... | | |
|
|
||||||
| | | future | | | | 4, result | | |
|
|
||||||
| | | ... | | | | 3, except | | |
|
|
||||||
+----------+ +------------+ +--------+ +-----------+ +---------+
|
|
||||||
|
|
||||||
Executor.submit() called:
|
|
||||||
- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict
|
|
||||||
- adds the id of the _WorkItem to the "Work Ids" queue
|
|
||||||
|
|
||||||
Local worker thread:
|
|
||||||
- reads work ids from the "Work Ids" queue and looks up the corresponding
|
|
||||||
WorkItem from the "Work Items" dict: if the work item has been cancelled then
|
|
||||||
it is simply removed from the dict, otherwise it is repackaged as a
|
|
||||||
_CallItem and put in the "Call Q". New _CallItems are put in the "Call Q"
|
|
||||||
until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because
|
|
||||||
calls placed in the "Call Q" can no longer be cancelled with Future.cancel().
|
|
||||||
- reads _ResultItems from "Result Q", updates the future stored in the
|
|
||||||
"Work Items" dict and deletes the dict entry
|
|
||||||
|
|
||||||
Process #1..n:
|
|
||||||
- reads _CallItems from "Call Q", executes the calls, and puts the resulting
|
|
||||||
_ResultItems in "Result Q"
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
|
|
||||||
|
|
||||||
import atexit
|
|
||||||
import os
|
|
||||||
from concurrent.futures import _base
|
|
||||||
import queue
|
|
||||||
from queue import Full
|
|
||||||
import multiprocessing
|
|
||||||
from multiprocessing import SimpleQueue
|
|
||||||
from multiprocessing.connection import wait
|
|
||||||
import threading
|
|
||||||
import weakref
|
|
||||||
from functools import partial
|
|
||||||
import itertools
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
# Workers are created as daemon threads and processes. This is done to allow the
|
|
||||||
# interpreter to exit when there are still idle processes in a
|
|
||||||
# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
|
|
||||||
# allowing workers to die with the interpreter has two undesirable properties:
|
|
||||||
# - The workers would still be running during interpreter shutdown,
|
|
||||||
# meaning that they would fail in unpredictable ways.
|
|
||||||
# - The workers could be killed while evaluating a work item, which could
|
|
||||||
# be bad if the callable being evaluated has external side-effects e.g.
|
|
||||||
# writing to a file.
|
|
||||||
#
|
|
||||||
# To work around this problem, an exit handler is installed which tells the
|
|
||||||
# workers to exit when their work queues are empty and then waits until the
|
|
||||||
# threads/processes finish.
|
|
||||||
|
|
||||||
_threads_queues = weakref.WeakKeyDictionary()
|
|
||||||
_shutdown = False
|
|
||||||
|
|
||||||
def _python_exit():
|
|
||||||
global _shutdown
|
|
||||||
_shutdown = True
|
|
||||||
items = list(_threads_queues.items())
|
|
||||||
for t, q in items:
|
|
||||||
q.put(None)
|
|
||||||
for t, q in items:
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
# Controls how many more calls than processes will be queued in the call queue.
|
|
||||||
# A smaller number will mean that processes spend more time idle waiting for
|
|
||||||
# work while a larger number will make Future.cancel() succeed less frequently
|
|
||||||
# (Futures in the call queue cannot be cancelled).
|
|
||||||
EXTRA_QUEUED_CALLS = 1
|
|
||||||
|
|
||||||
# Hack to embed stringification of remote traceback in local traceback
|
|
||||||
|
|
||||||
class _RemoteTraceback(Exception):
|
|
||||||
def __init__(self, tb):
|
|
||||||
self.tb = tb
|
|
||||||
def __str__(self):
|
|
||||||
return self.tb
|
|
||||||
|
|
||||||
class _ExceptionWithTraceback:
|
|
||||||
def __init__(self, exc, tb):
|
|
||||||
tb = traceback.format_exception(type(exc), exc, tb)
|
|
||||||
tb = ''.join(tb)
|
|
||||||
self.exc = exc
|
|
||||||
self.tb = '\n"""\n%s"""' % tb
|
|
||||||
def __reduce__(self):
|
|
||||||
return _rebuild_exc, (self.exc, self.tb)
|
|
||||||
|
|
||||||
def _rebuild_exc(exc, tb):
|
|
||||||
exc.__cause__ = _RemoteTraceback(tb)
|
|
||||||
return exc
|
|
||||||
|
|
||||||
class _WorkItem(object):
|
|
||||||
def __init__(self, future, fn, args, kwargs):
|
|
||||||
self.future = future
|
|
||||||
self.fn = fn
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
class _ResultItem(object):
|
|
||||||
def __init__(self, work_id, exception=None, result=None):
|
|
||||||
self.work_id = work_id
|
|
||||||
self.exception = exception
|
|
||||||
self.result = result
|
|
||||||
|
|
||||||
class _CallItem(object):
|
|
||||||
def __init__(self, work_id, fn, args, kwargs):
|
|
||||||
self.work_id = work_id
|
|
||||||
self.fn = fn
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def _get_chunks(*iterables, chunksize):
|
|
||||||
""" Iterates over zip()ed iterables in chunks. """
|
|
||||||
it = zip(*iterables)
|
|
||||||
while True:
|
|
||||||
chunk = tuple(itertools.islice(it, chunksize))
|
|
||||||
if not chunk:
|
|
||||||
return
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
def _process_chunk(fn, chunk):
|
|
||||||
""" Processes a chunk of an iterable passed to map.
|
|
||||||
|
|
||||||
Runs the function passed to map() on a chunk of the
|
|
||||||
iterable passed to map.
|
|
||||||
|
|
||||||
This function is run in a separate process.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return [fn(*args) for args in chunk]
|
|
||||||
|
|
||||||
def _process_worker(call_queue, result_queue):
|
|
||||||
"""Evaluates calls from call_queue and places the results in result_queue.
|
|
||||||
|
|
||||||
This worker is run in a separate process.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
call_queue: A multiprocessing.Queue of _CallItems that will be read and
|
|
||||||
evaluated by the worker.
|
|
||||||
result_queue: A multiprocessing.Queue of _ResultItems that will written
|
|
||||||
to by the worker.
|
|
||||||
shutdown: A multiprocessing.Event that will be set as a signal to the
|
|
||||||
worker that it should exit when call_queue is empty.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
call_item = call_queue.get(block=True)
|
|
||||||
if call_item is None:
|
|
||||||
# Wake up queue management thread
|
|
||||||
result_queue.put(os.getpid())
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
r = call_item.fn(*call_item.args, **call_item.kwargs)
|
|
||||||
except BaseException as e:
|
|
||||||
exc = _ExceptionWithTraceback(e, e.__traceback__)
|
|
||||||
result_queue.put(_ResultItem(call_item.work_id, exception=exc))
|
|
||||||
else:
|
|
||||||
result_queue.put(_ResultItem(call_item.work_id,
|
|
||||||
result=r))
|
|
||||||
|
|
||||||
def _add_call_item_to_queue(pending_work_items,
|
|
||||||
work_ids,
|
|
||||||
call_queue):
|
|
||||||
"""Fills call_queue with _WorkItems from pending_work_items.
|
|
||||||
|
|
||||||
This function never blocks.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pending_work_items: A dict mapping work ids to _WorkItems e.g.
|
|
||||||
{5: <_WorkItem...>, 6: <_WorkItem...>, ...}
|
|
||||||
work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
|
|
||||||
are consumed and the corresponding _WorkItems from
|
|
||||||
pending_work_items are transformed into _CallItems and put in
|
|
||||||
call_queue.
|
|
||||||
call_queue: A multiprocessing.Queue that will be filled with _CallItems
|
|
||||||
derived from _WorkItems.
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
if call_queue.full():
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
work_id = work_ids.get(block=False)
|
|
||||||
except queue.Empty:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
work_item = pending_work_items[work_id]
|
|
||||||
|
|
||||||
if work_item.future.set_running_or_notify_cancel():
|
|
||||||
call_queue.put(_CallItem(work_id,
|
|
||||||
work_item.fn,
|
|
||||||
work_item.args,
|
|
||||||
work_item.kwargs),
|
|
||||||
block=True)
|
|
||||||
else:
|
|
||||||
del pending_work_items[work_id]
|
|
||||||
continue
|
|
||||||
|
|
||||||
def _queue_management_worker(executor_reference,
|
|
||||||
processes,
|
|
||||||
pending_work_items,
|
|
||||||
work_ids_queue,
|
|
||||||
call_queue,
|
|
||||||
result_queue):
|
|
||||||
"""Manages the communication between this process and the worker processes.
|
|
||||||
|
|
||||||
This function is run in a local thread.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
|
|
||||||
this thread. Used to determine if the ProcessPoolExecutor has been
|
|
||||||
garbage collected and that this function can exit.
|
|
||||||
process: A list of the multiprocessing.Process instances used as
|
|
||||||
workers.
|
|
||||||
pending_work_items: A dict mapping work ids to _WorkItems e.g.
|
|
||||||
{5: <_WorkItem...>, 6: <_WorkItem...>, ...}
|
|
||||||
work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
|
|
||||||
call_queue: A multiprocessing.Queue that will be filled with _CallItems
|
|
||||||
derived from _WorkItems for processing by the process workers.
|
|
||||||
result_queue: A multiprocessing.Queue of _ResultItems generated by the
|
|
||||||
process workers.
|
|
||||||
"""
|
|
||||||
executor = None
|
|
||||||
|
|
||||||
def shutting_down():
|
|
||||||
return _shutdown or executor is None or executor._shutdown_thread
|
|
||||||
|
|
||||||
def shutdown_worker():
|
|
||||||
# This is an upper bound
|
|
||||||
nb_children_alive = sum(p.is_alive() for p in processes.values())
|
|
||||||
for i in range(0, nb_children_alive):
|
|
||||||
call_queue.put_nowait(None)
|
|
||||||
# Release the queue's resources as soon as possible.
|
|
||||||
call_queue.close()
|
|
||||||
# If .join() is not called on the created processes then
|
|
||||||
# some multiprocessing.Queue methods may deadlock on Mac OS X.
|
|
||||||
for p in processes.values():
|
|
||||||
p.join()
|
|
||||||
|
|
||||||
reader = result_queue._reader
|
|
||||||
|
|
||||||
while True:
|
|
||||||
_add_call_item_to_queue(pending_work_items,
|
|
||||||
work_ids_queue,
|
|
||||||
call_queue)
|
|
||||||
|
|
||||||
sentinels = [p.sentinel for p in processes.values()]
|
|
||||||
assert sentinels
|
|
||||||
ready = wait([reader] + sentinels)
|
|
||||||
if reader in ready:
|
|
||||||
result_item = reader.recv()
|
|
||||||
else:
|
|
||||||
# Mark the process pool broken so that submits fail right now.
|
|
||||||
executor = executor_reference()
|
|
||||||
if executor is not None:
|
|
||||||
executor._broken = True
|
|
||||||
executor._shutdown_thread = True
|
|
||||||
executor = None
|
|
||||||
# All futures in flight must be marked failed
|
|
||||||
for work_id, work_item in pending_work_items.items():
|
|
||||||
work_item.future.set_exception(
|
|
||||||
BrokenProcessPool(
|
|
||||||
"A process in the process pool was "
|
|
||||||
"terminated abruptly while the future was "
|
|
||||||
"running or pending."
|
|
||||||
))
|
|
||||||
# Delete references to object. See issue16284
|
|
||||||
del work_item
|
|
||||||
pending_work_items.clear()
|
|
||||||
# Terminate remaining workers forcibly: the queues or their
|
|
||||||
# locks may be in a dirty state and block forever.
|
|
||||||
for p in processes.values():
|
|
||||||
p.terminate()
|
|
||||||
shutdown_worker()
|
|
||||||
return
|
|
||||||
if isinstance(result_item, int):
|
|
||||||
# Clean shutdown of a worker using its PID
|
|
||||||
# (avoids marking the executor broken)
|
|
||||||
assert shutting_down()
|
|
||||||
p = processes.pop(result_item)
|
|
||||||
p.join()
|
|
||||||
if not processes:
|
|
||||||
shutdown_worker()
|
|
||||||
return
|
|
||||||
elif result_item is not None:
|
|
||||||
work_item = pending_work_items.pop(result_item.work_id, None)
|
|
||||||
# work_item can be None if another process terminated (see above)
|
|
||||||
if work_item is not None:
|
|
||||||
if result_item.exception:
|
|
||||||
work_item.future.set_exception(result_item.exception)
|
|
||||||
else:
|
|
||||||
work_item.future.set_result(result_item.result)
|
|
||||||
# Delete references to object. See issue16284
|
|
||||||
del work_item
|
|
||||||
# Check whether we should start shutting down.
|
|
||||||
executor = executor_reference()
|
|
||||||
# No more work items can be added if:
|
|
||||||
# - The interpreter is shutting down OR
|
|
||||||
# - The executor that owns this worker has been collected OR
|
|
||||||
# - The executor that owns this worker has been shutdown.
|
|
||||||
if shutting_down():
|
|
||||||
try:
|
|
||||||
# Since no new work items can be added, it is safe to shutdown
|
|
||||||
# this thread if there are no pending work items.
|
|
||||||
if not pending_work_items:
|
|
||||||
shutdown_worker()
|
|
||||||
return
|
|
||||||
except Full:
|
|
||||||
# This is not a problem: we will eventually be woken up (in
|
|
||||||
# result_queue.get()) and be able to send a sentinel again.
|
|
||||||
pass
|
|
||||||
executor = None
|
|
||||||
|
|
||||||
_system_limits_checked = False
|
|
||||||
_system_limited = None
|
|
||||||
def _check_system_limits():
|
|
||||||
global _system_limits_checked, _system_limited
|
|
||||||
if _system_limits_checked:
|
|
||||||
if _system_limited:
|
|
||||||
raise NotImplementedError(_system_limited)
|
|
||||||
_system_limits_checked = True
|
|
||||||
try:
|
|
||||||
nsems_max = os.sysconf("SC_SEM_NSEMS_MAX")
|
|
||||||
except (AttributeError, ValueError):
|
|
||||||
# sysconf not available or setting not available
|
|
||||||
return
|
|
||||||
if nsems_max == -1:
|
|
||||||
# indetermined limit, assume that limit is determined
|
|
||||||
# by available memory only
|
|
||||||
return
|
|
||||||
if nsems_max >= 256:
|
|
||||||
# minimum number of semaphores available
|
|
||||||
# according to POSIX
|
|
||||||
return
|
|
||||||
_system_limited = "system provides too few semaphores (%d available, 256 necessary)" % nsems_max
|
|
||||||
raise NotImplementedError(_system_limited)
|
|
||||||
|
|
||||||
|
|
||||||
class BrokenProcessPool(RuntimeError):
|
|
||||||
"""
|
|
||||||
Raised when a process in a ProcessPoolExecutor terminated abruptly
|
|
||||||
while a future was in the running state.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessPoolExecutor(_base.Executor):
|
|
||||||
def __init__(self, max_workers=None):
|
|
||||||
"""Initializes a new ProcessPoolExecutor instance.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
max_workers: The maximum number of processes that can be used to
|
|
||||||
execute the given calls. If None or not given then as many
|
|
||||||
worker processes will be created as the machine has processors.
|
|
||||||
"""
|
|
||||||
_check_system_limits()
|
|
||||||
|
|
||||||
if max_workers is None:
|
|
||||||
self._max_workers = os.cpu_count() or 1
|
|
||||||
else:
|
|
||||||
if max_workers <= 0:
|
|
||||||
raise ValueError("max_workers must be greater than 0")
|
|
||||||
|
|
||||||
self._max_workers = max_workers
|
|
||||||
|
|
||||||
# Make the call queue slightly larger than the number of processes to
|
|
||||||
# prevent the worker processes from idling. But don't make it too big
|
|
||||||
# because futures in the call queue cannot be cancelled.
|
|
||||||
self._call_queue = multiprocessing.Queue(self._max_workers +
|
|
||||||
EXTRA_QUEUED_CALLS)
|
|
||||||
# Killed worker processes can produce spurious "broken pipe"
|
|
||||||
# tracebacks in the queue's own worker thread. But we detect killed
|
|
||||||
# processes anyway, so silence the tracebacks.
|
|
||||||
self._call_queue._ignore_epipe = True
|
|
||||||
self._result_queue = SimpleQueue()
|
|
||||||
self._work_ids = queue.Queue()
|
|
||||||
self._queue_management_thread = None
|
|
||||||
# Map of pids to processes
|
|
||||||
self._processes = {}
|
|
||||||
|
|
||||||
# Shutdown is a two-step process.
|
|
||||||
self._shutdown_thread = False
|
|
||||||
self._shutdown_lock = threading.Lock()
|
|
||||||
self._broken = False
|
|
||||||
self._queue_count = 0
|
|
||||||
self._pending_work_items = {}
|
|
||||||
|
|
||||||
def _start_queue_management_thread(self):
|
|
||||||
# When the executor gets lost, the weakref callback will wake up
|
|
||||||
# the queue management thread.
|
|
||||||
def weakref_cb(_, q=self._result_queue):
|
|
||||||
q.put(None)
|
|
||||||
if self._queue_management_thread is None:
|
|
||||||
# Start the processes so that their sentinels are known.
|
|
||||||
self._adjust_process_count()
|
|
||||||
self._queue_management_thread = threading.Thread(
|
|
||||||
target=_queue_management_worker,
|
|
||||||
args=(weakref.ref(self, weakref_cb),
|
|
||||||
self._processes,
|
|
||||||
self._pending_work_items,
|
|
||||||
self._work_ids,
|
|
||||||
self._call_queue,
|
|
||||||
self._result_queue))
|
|
||||||
self._queue_management_thread.daemon = True
|
|
||||||
self._queue_management_thread.start()
|
|
||||||
_threads_queues[self._queue_management_thread] = self._result_queue
|
|
||||||
|
|
||||||
def _adjust_process_count(self):
|
|
||||||
for _ in range(len(self._processes), self._max_workers):
|
|
||||||
p = multiprocessing.Process(
|
|
||||||
target=_process_worker,
|
|
||||||
args=(self._call_queue,
|
|
||||||
self._result_queue))
|
|
||||||
p.start()
|
|
||||||
self._processes[p.pid] = p
|
|
||||||
|
|
||||||
def submit(self, fn, *args, **kwargs):
|
|
||||||
with self._shutdown_lock:
|
|
||||||
if self._broken:
|
|
||||||
raise BrokenProcessPool('A child process terminated '
|
|
||||||
'abruptly, the process pool is not usable anymore')
|
|
||||||
if self._shutdown_thread:
|
|
||||||
raise RuntimeError('cannot schedule new futures after shutdown')
|
|
||||||
|
|
||||||
f = _base.Future()
|
|
||||||
w = _WorkItem(f, fn, args, kwargs)
|
|
||||||
|
|
||||||
self._pending_work_items[self._queue_count] = w
|
|
||||||
self._work_ids.put(self._queue_count)
|
|
||||||
self._queue_count += 1
|
|
||||||
# Wake up queue management thread
|
|
||||||
self._result_queue.put(None)
|
|
||||||
|
|
||||||
self._start_queue_management_thread()
|
|
||||||
return f
|
|
||||||
submit.__doc__ = _base.Executor.submit.__doc__
|
|
||||||
|
|
||||||
def map(self, fn, *iterables, timeout=None, chunksize=1):
|
|
||||||
"""Returns an iterator equivalent to map(fn, iter).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fn: A callable that will take as many arguments as there are
|
|
||||||
passed iterables.
|
|
||||||
timeout: The maximum number of seconds to wait. If None, then there
|
|
||||||
is no limit on the wait time.
|
|
||||||
chunksize: If greater than one, the iterables will be chopped into
|
|
||||||
chunks of size chunksize and submitted to the process pool.
|
|
||||||
If set to one, the items in the list will be sent one at a time.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An iterator equivalent to: map(func, *iterables) but the calls may
|
|
||||||
be evaluated out-of-order.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
TimeoutError: If the entire result iterator could not be generated
|
|
||||||
before the given timeout.
|
|
||||||
Exception: If fn(*args) raises for any values.
|
|
||||||
"""
|
|
||||||
if chunksize < 1:
|
|
||||||
raise ValueError("chunksize must be >= 1.")
|
|
||||||
|
|
||||||
results = super().map(partial(_process_chunk, fn),
|
|
||||||
_get_chunks(*iterables, chunksize=chunksize),
|
|
||||||
timeout=timeout)
|
|
||||||
return itertools.chain.from_iterable(results)
|
|
||||||
|
|
||||||
def shutdown(self, wait=True):
|
|
||||||
with self._shutdown_lock:
|
|
||||||
self._shutdown_thread = True
|
|
||||||
if self._queue_management_thread:
|
|
||||||
# Wake up queue management thread
|
|
||||||
self._result_queue.put(None)
|
|
||||||
if wait:
|
|
||||||
self._queue_management_thread.join()
|
|
||||||
# To reduce the risk of opening too many files, remove references to
|
|
||||||
# objects that use file descriptors.
|
|
||||||
self._queue_management_thread = None
|
|
||||||
self._call_queue = None
|
|
||||||
self._result_queue = None
|
|
||||||
self._processes = None
|
|
||||||
shutdown.__doc__ = _base.Executor.shutdown.__doc__
|
|
||||||
|
|
||||||
atexit.register(_python_exit)
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
# Copyright 2009 Brian Quinlan. All Rights Reserved.
|
|
||||||
# Licensed to PSF under a Contributor Agreement.
|
|
||||||
|
|
||||||
"""Implements ThreadPoolExecutor."""
|
|
||||||
|
|
||||||
__author__ = 'Brian Quinlan (brian@sweetapp.com)'
|
|
||||||
|
|
||||||
import atexit
|
|
||||||
from concurrent.futures import _base
|
|
||||||
import queue
|
|
||||||
import threading
|
|
||||||
import weakref
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Workers are created as daemon threads. This is done to allow the interpreter
|
|
||||||
# to exit when there are still idle threads in a ThreadPoolExecutor's thread
|
|
||||||
# pool (i.e. shutdown() was not called). However, allowing workers to die with
|
|
||||||
# the interpreter has two undesirable properties:
|
|
||||||
# - The workers would still be running during interpreter shutdown,
|
|
||||||
# meaning that they would fail in unpredictable ways.
|
|
||||||
# - The workers could be killed while evaluating a work item, which could
|
|
||||||
# be bad if the callable being evaluated has external side-effects e.g.
|
|
||||||
# writing to a file.
|
|
||||||
#
|
|
||||||
# To work around this problem, an exit handler is installed which tells the
|
|
||||||
# workers to exit when their work queues are empty and then waits until the
|
|
||||||
# threads finish.
|
|
||||||
|
|
||||||
_threads_queues = weakref.WeakKeyDictionary()
|
|
||||||
_shutdown = False
|
|
||||||
|
|
||||||
def _python_exit():
|
|
||||||
global _shutdown
|
|
||||||
_shutdown = True
|
|
||||||
items = list(_threads_queues.items())
|
|
||||||
for t, q in items:
|
|
||||||
q.put(None)
|
|
||||||
for t, q in items:
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
atexit.register(_python_exit)
|
|
||||||
|
|
||||||
class _WorkItem(object):
|
|
||||||
def __init__(self, future, fn, args, kwargs):
|
|
||||||
self.future = future
|
|
||||||
self.fn = fn
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.future.set_running_or_notify_cancel():
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = self.fn(*self.args, **self.kwargs)
|
|
||||||
except BaseException as e:
|
|
||||||
self.future.set_exception(e)
|
|
||||||
else:
|
|
||||||
self.future.set_result(result)
|
|
||||||
|
|
||||||
def _worker(executor_reference, work_queue):
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
work_item = work_queue.get(block=True)
|
|
||||||
if work_item is not None:
|
|
||||||
work_item.run()
|
|
||||||
# Delete references to object. See issue16284
|
|
||||||
del work_item
|
|
||||||
continue
|
|
||||||
executor = executor_reference()
|
|
||||||
# Exit if:
|
|
||||||
# - The interpreter is shutting down OR
|
|
||||||
# - The executor that owns the worker has been collected OR
|
|
||||||
# - The executor that owns the worker has been shutdown.
|
|
||||||
if _shutdown or executor is None or executor._shutdown:
|
|
||||||
# Notice other workers
|
|
||||||
work_queue.put(None)
|
|
||||||
return
|
|
||||||
del executor
|
|
||||||
except BaseException:
|
|
||||||
_base.LOGGER.critical('Exception in worker', exc_info=True)
|
|
||||||
|
|
||||||
class ThreadPoolExecutor(_base.Executor):
|
|
||||||
def __init__(self, max_workers=None, thread_name_prefix=''):
|
|
||||||
"""Initializes a new ThreadPoolExecutor instance.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
max_workers: The maximum number of threads that can be used to
|
|
||||||
execute the given calls.
|
|
||||||
thread_name_prefix: An optional name prefix to give our threads.
|
|
||||||
"""
|
|
||||||
if max_workers is None:
|
|
||||||
# Use this number because ThreadPoolExecutor is often
|
|
||||||
# used to overlap I/O instead of CPU work.
|
|
||||||
max_workers = (os.cpu_count() or 1) * 5
|
|
||||||
if max_workers <= 0:
|
|
||||||
raise ValueError("max_workers must be greater than 0")
|
|
||||||
|
|
||||||
self._max_workers = max_workers
|
|
||||||
self._work_queue = queue.Queue()
|
|
||||||
self._threads = set()
|
|
||||||
self._shutdown = False
|
|
||||||
self._shutdown_lock = threading.Lock()
|
|
||||||
self._thread_name_prefix = thread_name_prefix
|
|
||||||
|
|
||||||
def submit(self, fn, *args, **kwargs):
|
|
||||||
with self._shutdown_lock:
|
|
||||||
if self._shutdown:
|
|
||||||
raise RuntimeError('cannot schedule new futures after shutdown')
|
|
||||||
|
|
||||||
f = _base.Future()
|
|
||||||
w = _WorkItem(f, fn, args, kwargs)
|
|
||||||
|
|
||||||
self._work_queue.put(w)
|
|
||||||
self._adjust_thread_count()
|
|
||||||
return f
|
|
||||||
submit.__doc__ = _base.Executor.submit.__doc__
|
|
||||||
|
|
||||||
def _adjust_thread_count(self):
|
|
||||||
# When the executor gets lost, the weakref callback will wake up
|
|
||||||
# the worker threads.
|
|
||||||
def weakref_cb(_, q=self._work_queue):
|
|
||||||
q.put(None)
|
|
||||||
# TODO(bquinlan): Should avoid creating new threads if there are more
|
|
||||||
# idle threads than items in the work queue.
|
|
||||||
num_threads = len(self._threads)
|
|
||||||
if num_threads < self._max_workers:
|
|
||||||
thread_name = '%s_%d' % (self._thread_name_prefix or self,
|
|
||||||
num_threads)
|
|
||||||
t = threading.Thread(name=thread_name, target=_worker,
|
|
||||||
args=(weakref.ref(self, weakref_cb),
|
|
||||||
self._work_queue))
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
self._threads.add(t)
|
|
||||||
_threads_queues[t] = self._work_queue
|
|
||||||
|
|
||||||
def shutdown(self, wait=True):
|
|
||||||
with self._shutdown_lock:
|
|
||||||
self._shutdown = True
|
|
||||||
self._work_queue.put(None)
|
|
||||||
if wait:
|
|
||||||
for t in self._threads:
|
|
||||||
t.join()
|
|
||||||
shutdown.__doc__ = _base.Executor.shutdown.__doc__
|
|
||||||
1339
Lib/configparser.py
1339
Lib/configparser.py
File diff suppressed because it is too large
Load Diff
@@ -1,677 +0,0 @@
|
|||||||
"""Utilities for with-statement contexts. See PEP 343."""
|
|
||||||
import abc
|
|
||||||
import sys
|
|
||||||
import _collections_abc
|
|
||||||
from collections import deque
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
|
||||||
"AbstractContextManager", "AbstractAsyncContextManager",
|
|
||||||
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
|
||||||
"redirect_stdout", "redirect_stderr", "suppress"]
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractContextManager(abc.ABC):
|
|
||||||
|
|
||||||
"""An abstract base class for context managers."""
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Return `self` upon entering the runtime context."""
|
|
||||||
return self
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
"""Raise any exception triggered within the runtime context."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is AbstractContextManager:
|
|
||||||
return _collections_abc._check_methods(C, "__enter__", "__exit__")
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractAsyncContextManager(abc.ABC):
|
|
||||||
|
|
||||||
"""An abstract base class for asynchronous context managers."""
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
"""Return `self` upon entering the runtime context."""
|
|
||||||
return self
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
||||||
"""Raise any exception triggered within the runtime context."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __subclasshook__(cls, C):
|
|
||||||
if cls is AbstractAsyncContextManager:
|
|
||||||
return _collections_abc._check_methods(C, "__aenter__",
|
|
||||||
"__aexit__")
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
class ContextDecorator(object):
|
|
||||||
"A base class or mixin that enables context managers to work as decorators."
|
|
||||||
|
|
||||||
def _recreate_cm(self):
|
|
||||||
"""Return a recreated instance of self.
|
|
||||||
|
|
||||||
Allows an otherwise one-shot context manager like
|
|
||||||
_GeneratorContextManager to support use as
|
|
||||||
a decorator via implicit recreation.
|
|
||||||
|
|
||||||
This is a private interface just for _GeneratorContextManager.
|
|
||||||
See issue #11647 for details.
|
|
||||||
"""
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __call__(self, func):
|
|
||||||
@wraps(func)
|
|
||||||
def inner(*args, **kwds):
|
|
||||||
with self._recreate_cm():
|
|
||||||
return func(*args, **kwds)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManagerBase:
|
|
||||||
"""Shared functionality for @contextmanager and @asynccontextmanager."""
|
|
||||||
|
|
||||||
def __init__(self, func, args, kwds):
|
|
||||||
self.gen = func(*args, **kwds)
|
|
||||||
self.func, self.args, self.kwds = func, args, kwds
|
|
||||||
# Issue 19330: ensure context manager instances have good docstrings
|
|
||||||
doc = getattr(func, "__doc__", None)
|
|
||||||
if doc is None:
|
|
||||||
doc = type(self).__doc__
|
|
||||||
self.__doc__ = doc
|
|
||||||
# Unfortunately, this still doesn't provide good help output when
|
|
||||||
# inspecting the created context manager instances, since pydoc
|
|
||||||
# currently bypasses the instance docstring and shows the docstring
|
|
||||||
# for the class instead.
|
|
||||||
# See http://bugs.python.org/issue19404 for more details.
|
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManager(_GeneratorContextManagerBase,
|
|
||||||
AbstractContextManager,
|
|
||||||
ContextDecorator):
|
|
||||||
"""Helper for @contextmanager decorator."""
|
|
||||||
|
|
||||||
def _recreate_cm(self):
|
|
||||||
# _GCM instances are one-shot context managers, so the
|
|
||||||
# CM must be recreated each time a decorated function is
|
|
||||||
# called
|
|
||||||
return self.__class__(self.func, self.args, self.kwds)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
# do not keep args and kwds alive unnecessarily
|
|
||||||
# they are only needed for recreation, which is not possible anymore
|
|
||||||
del self.args, self.kwds, self.func
|
|
||||||
try:
|
|
||||||
return next(self.gen)
|
|
||||||
except StopIteration:
|
|
||||||
raise RuntimeError("generator didn't yield") from None
|
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
if type is None:
|
|
||||||
try:
|
|
||||||
next(self.gen)
|
|
||||||
except StopIteration:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
raise RuntimeError("generator didn't stop")
|
|
||||||
else:
|
|
||||||
if value is None:
|
|
||||||
# Need to force instantiation so we can reliably
|
|
||||||
# tell if we get the same exception back
|
|
||||||
value = type()
|
|
||||||
try:
|
|
||||||
self.gen.throw(type, value, traceback)
|
|
||||||
except StopIteration as exc:
|
|
||||||
# Suppress StopIteration *unless* it's the same exception that
|
|
||||||
# was passed to throw(). This prevents a StopIteration
|
|
||||||
# raised inside the "with" statement from being suppressed.
|
|
||||||
return exc is not value
|
|
||||||
except RuntimeError as exc:
|
|
||||||
# Don't re-raise the passed in exception. (issue27122)
|
|
||||||
if exc is value:
|
|
||||||
return False
|
|
||||||
# Likewise, avoid suppressing if a StopIteration exception
|
|
||||||
# was passed to throw() and later wrapped into a RuntimeError
|
|
||||||
# (see PEP 479).
|
|
||||||
if type is StopIteration and exc.__cause__ is value:
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
# only re-raise if it's *not* the exception that was
|
|
||||||
# passed to throw(), because __exit__() must not raise
|
|
||||||
# an exception unless __exit__() itself failed. But throw()
|
|
||||||
# has to raise the exception to signal propagation, so this
|
|
||||||
# fixes the impedance mismatch between the throw() protocol
|
|
||||||
# and the __exit__() protocol.
|
|
||||||
#
|
|
||||||
# This cannot use 'except BaseException as exc' (as in the
|
|
||||||
# async implementation) to maintain compatibility with
|
|
||||||
# Python 2, where old-style class exceptions are not caught
|
|
||||||
# by 'except BaseException'.
|
|
||||||
if sys.exc_info()[1] is value:
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
|
||||||
|
|
||||||
|
|
||||||
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
|
||||||
AbstractAsyncContextManager):
|
|
||||||
"""Helper for @asynccontextmanager."""
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
try:
|
|
||||||
return await self.gen.__anext__()
|
|
||||||
except StopAsyncIteration:
|
|
||||||
raise RuntimeError("generator didn't yield") from None
|
|
||||||
|
|
||||||
async def __aexit__(self, typ, value, traceback):
|
|
||||||
if typ is None:
|
|
||||||
try:
|
|
||||||
await self.gen.__anext__()
|
|
||||||
except StopAsyncIteration:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise RuntimeError("generator didn't stop")
|
|
||||||
else:
|
|
||||||
if value is None:
|
|
||||||
value = typ()
|
|
||||||
# See _GeneratorContextManager.__exit__ for comments on subtleties
|
|
||||||
# in this implementation
|
|
||||||
try:
|
|
||||||
await self.gen.athrow(typ, value, traceback)
|
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
|
||||||
except StopAsyncIteration as exc:
|
|
||||||
return exc is not value
|
|
||||||
except RuntimeError as exc:
|
|
||||||
if exc is value:
|
|
||||||
return False
|
|
||||||
# Avoid suppressing if a StopIteration exception
|
|
||||||
# was passed to throw() and later wrapped into a RuntimeError
|
|
||||||
# (see PEP 479 for sync generators; async generators also
|
|
||||||
# have this behavior). But do this only if the exception wrapped
|
|
||||||
# by the RuntimeError is actully Stop(Async)Iteration (see
|
|
||||||
# issue29692).
|
|
||||||
if isinstance(value, (StopIteration, StopAsyncIteration)):
|
|
||||||
if exc.__cause__ is value:
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
except BaseException as exc:
|
|
||||||
if exc is not value:
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def contextmanager(func):
|
|
||||||
"""@contextmanager decorator.
|
|
||||||
|
|
||||||
Typical usage:
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def some_generator(<arguments>):
|
|
||||||
<setup>
|
|
||||||
try:
|
|
||||||
yield <value>
|
|
||||||
finally:
|
|
||||||
<cleanup>
|
|
||||||
|
|
||||||
This makes this:
|
|
||||||
|
|
||||||
with some_generator(<arguments>) as <variable>:
|
|
||||||
<body>
|
|
||||||
|
|
||||||
equivalent to this:
|
|
||||||
|
|
||||||
<setup>
|
|
||||||
try:
|
|
||||||
<variable> = <value>
|
|
||||||
<body>
|
|
||||||
finally:
|
|
||||||
<cleanup>
|
|
||||||
"""
|
|
||||||
@wraps(func)
|
|
||||||
def helper(*args, **kwds):
|
|
||||||
return _GeneratorContextManager(func, args, kwds)
|
|
||||||
return helper
|
|
||||||
|
|
||||||
|
|
||||||
def asynccontextmanager(func):
|
|
||||||
"""@asynccontextmanager decorator.
|
|
||||||
|
|
||||||
Typical usage:
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def some_async_generator(<arguments>):
|
|
||||||
<setup>
|
|
||||||
try:
|
|
||||||
yield <value>
|
|
||||||
finally:
|
|
||||||
<cleanup>
|
|
||||||
|
|
||||||
This makes this:
|
|
||||||
|
|
||||||
async with some_async_generator(<arguments>) as <variable>:
|
|
||||||
<body>
|
|
||||||
|
|
||||||
equivalent to this:
|
|
||||||
|
|
||||||
<setup>
|
|
||||||
try:
|
|
||||||
<variable> = <value>
|
|
||||||
<body>
|
|
||||||
finally:
|
|
||||||
<cleanup>
|
|
||||||
"""
|
|
||||||
@wraps(func)
|
|
||||||
def helper(*args, **kwds):
|
|
||||||
return _AsyncGeneratorContextManager(func, args, kwds)
|
|
||||||
return helper
|
|
||||||
|
|
||||||
|
|
||||||
class closing(AbstractContextManager):
|
|
||||||
"""Context to automatically close something at the end of a block.
|
|
||||||
|
|
||||||
Code like this:
|
|
||||||
|
|
||||||
with closing(<module>.open(<arguments>)) as f:
|
|
||||||
<block>
|
|
||||||
|
|
||||||
is equivalent to this:
|
|
||||||
|
|
||||||
f = <module>.open(<arguments>)
|
|
||||||
try:
|
|
||||||
<block>
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, thing):
|
|
||||||
self.thing = thing
|
|
||||||
def __enter__(self):
|
|
||||||
return self.thing
|
|
||||||
def __exit__(self, *exc_info):
|
|
||||||
self.thing.close()
|
|
||||||
|
|
||||||
|
|
||||||
class _RedirectStream(AbstractContextManager):
|
|
||||||
|
|
||||||
_stream = None
|
|
||||||
|
|
||||||
def __init__(self, new_target):
|
|
||||||
self._new_target = new_target
|
|
||||||
# We use a list of old targets to make this CM re-entrant
|
|
||||||
self._old_targets = []
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._old_targets.append(getattr(sys, self._stream))
|
|
||||||
setattr(sys, self._stream, self._new_target)
|
|
||||||
return self._new_target
|
|
||||||
|
|
||||||
def __exit__(self, exctype, excinst, exctb):
|
|
||||||
setattr(sys, self._stream, self._old_targets.pop())
|
|
||||||
|
|
||||||
|
|
||||||
class redirect_stdout(_RedirectStream):
|
|
||||||
"""Context manager for temporarily redirecting stdout to another file.
|
|
||||||
|
|
||||||
# How to send help() to stderr
|
|
||||||
with redirect_stdout(sys.stderr):
|
|
||||||
help(dir)
|
|
||||||
|
|
||||||
# How to write help() to a file
|
|
||||||
with open('help.txt', 'w') as f:
|
|
||||||
with redirect_stdout(f):
|
|
||||||
help(pow)
|
|
||||||
"""
|
|
||||||
|
|
||||||
_stream = "stdout"
|
|
||||||
|
|
||||||
|
|
||||||
class redirect_stderr(_RedirectStream):
|
|
||||||
"""Context manager for temporarily redirecting stderr to another file."""
|
|
||||||
|
|
||||||
_stream = "stderr"
|
|
||||||
|
|
||||||
|
|
||||||
class suppress(AbstractContextManager):
|
|
||||||
"""Context manager to suppress specified exceptions
|
|
||||||
|
|
||||||
After the exception is suppressed, execution proceeds with the next
|
|
||||||
statement following the with statement.
|
|
||||||
|
|
||||||
with suppress(FileNotFoundError):
|
|
||||||
os.remove(somefile)
|
|
||||||
# Execution still resumes here if the file was already removed
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *exceptions):
|
|
||||||
self._exceptions = exceptions
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __exit__(self, exctype, excinst, exctb):
|
|
||||||
# Unlike isinstance and issubclass, CPython exception handling
|
|
||||||
# currently only looks at the concrete type hierarchy (ignoring
|
|
||||||
# the instance and subclass checking hooks). While Guido considers
|
|
||||||
# that a bug rather than a feature, it's a fairly hard one to fix
|
|
||||||
# due to various internal implementation details. suppress provides
|
|
||||||
# the simpler issubclass based semantics, rather than trying to
|
|
||||||
# exactly reproduce the limitations of the CPython interpreter.
|
|
||||||
#
|
|
||||||
# See http://bugs.python.org/issue12029 for more details
|
|
||||||
return exctype is not None and issubclass(exctype, self._exceptions)
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseExitStack:
|
|
||||||
"""A base class for ExitStack and AsyncExitStack."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_exit_wrapper(cm, cm_exit):
|
|
||||||
def _exit_wrapper(exc_type, exc, tb):
|
|
||||||
return cm_exit(cm, exc_type, exc, tb)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_cb_wrapper(callback, *args, **kwds):
|
|
||||||
def _exit_wrapper(exc_type, exc, tb):
|
|
||||||
callback(*args, **kwds)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._exit_callbacks = deque()
|
|
||||||
|
|
||||||
def pop_all(self):
|
|
||||||
"""Preserve the context stack by transferring it to a new instance."""
|
|
||||||
new_stack = type(self)()
|
|
||||||
new_stack._exit_callbacks = self._exit_callbacks
|
|
||||||
self._exit_callbacks = deque()
|
|
||||||
return new_stack
|
|
||||||
|
|
||||||
def push(self, exit):
|
|
||||||
"""Registers a callback with the standard __exit__ method signature.
|
|
||||||
|
|
||||||
Can suppress exceptions the same way __exit__ method can.
|
|
||||||
Also accepts any object with an __exit__ method (registering a call
|
|
||||||
to the method instead of the object itself).
|
|
||||||
"""
|
|
||||||
# We use an unbound method rather than a bound method to follow
|
|
||||||
# the standard lookup behaviour for special methods.
|
|
||||||
_cb_type = type(exit)
|
|
||||||
|
|
||||||
try:
|
|
||||||
exit_method = _cb_type.__exit__
|
|
||||||
except AttributeError:
|
|
||||||
# Not a context manager, so assume it's a callable.
|
|
||||||
self._push_exit_callback(exit)
|
|
||||||
else:
|
|
||||||
self._push_cm_exit(exit, exit_method)
|
|
||||||
return exit # Allow use as a decorator.
|
|
||||||
|
|
||||||
def enter_context(self, cm):
|
|
||||||
"""Enters the supplied context manager.
|
|
||||||
|
|
||||||
If successful, also pushes its __exit__ method as a callback and
|
|
||||||
returns the result of the __enter__ method.
|
|
||||||
"""
|
|
||||||
# We look up the special methods on the type to match the with
|
|
||||||
# statement.
|
|
||||||
_cm_type = type(cm)
|
|
||||||
_exit = _cm_type.__exit__
|
|
||||||
result = _cm_type.__enter__(cm)
|
|
||||||
self._push_cm_exit(cm, _exit)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def callback(self, callback, *args, **kwds):
|
|
||||||
"""Registers an arbitrary callback and arguments.
|
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
|
||||||
"""
|
|
||||||
_exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
|
|
||||||
|
|
||||||
# We changed the signature, so using @wraps is not appropriate, but
|
|
||||||
# setting __wrapped__ may still help with introspection.
|
|
||||||
_exit_wrapper.__wrapped__ = callback
|
|
||||||
self._push_exit_callback(_exit_wrapper)
|
|
||||||
return callback # Allow use as a decorator
|
|
||||||
|
|
||||||
def _push_cm_exit(self, cm, cm_exit):
|
|
||||||
"""Helper to correctly register callbacks to __exit__ methods."""
|
|
||||||
_exit_wrapper = self._create_exit_wrapper(cm, cm_exit)
|
|
||||||
_exit_wrapper.__self__ = cm
|
|
||||||
self._push_exit_callback(_exit_wrapper, True)
|
|
||||||
|
|
||||||
def _push_exit_callback(self, callback, is_sync=True):
|
|
||||||
self._exit_callbacks.append((is_sync, callback))
|
|
||||||
|
|
||||||
|
|
||||||
# Inspired by discussions on http://bugs.python.org/issue13585
|
|
||||||
class ExitStack(_BaseExitStack, AbstractContextManager):
|
|
||||||
"""Context manager for dynamic management of a stack of exit callbacks.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
with ExitStack() as stack:
|
|
||||||
files = [stack.enter_context(open(fname)) for fname in filenames]
|
|
||||||
# All opened files will automatically be closed at the end of
|
|
||||||
# the with statement, even if attempts to open files later
|
|
||||||
# in the list raise an exception.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *exc_details):
|
|
||||||
received_exc = exc_details[0] is not None
|
|
||||||
|
|
||||||
# We manipulate the exception state so it behaves as though
|
|
||||||
# we were actually nesting multiple with statements
|
|
||||||
frame_exc = sys.exc_info()[1]
|
|
||||||
def _fix_exception_context(new_exc, old_exc):
|
|
||||||
# Context may not be correct, so find the end of the chain
|
|
||||||
while 1:
|
|
||||||
exc_context = new_exc.__context__
|
|
||||||
if exc_context is old_exc:
|
|
||||||
# Context is already set correctly (see issue 20317)
|
|
||||||
return
|
|
||||||
if exc_context is None or exc_context is frame_exc:
|
|
||||||
break
|
|
||||||
new_exc = exc_context
|
|
||||||
# Change the end of the chain to point to the exception
|
|
||||||
# we expect it to reference
|
|
||||||
new_exc.__context__ = old_exc
|
|
||||||
|
|
||||||
# Callbacks are invoked in LIFO order to match the behaviour of
|
|
||||||
# nested context managers
|
|
||||||
suppressed_exc = False
|
|
||||||
pending_raise = False
|
|
||||||
while self._exit_callbacks:
|
|
||||||
is_sync, cb = self._exit_callbacks.pop()
|
|
||||||
assert is_sync
|
|
||||||
try:
|
|
||||||
if cb(*exc_details):
|
|
||||||
suppressed_exc = True
|
|
||||||
pending_raise = False
|
|
||||||
exc_details = (None, None, None)
|
|
||||||
except:
|
|
||||||
new_exc_details = sys.exc_info()
|
|
||||||
# simulate the stack of exceptions by setting the context
|
|
||||||
_fix_exception_context(new_exc_details[1], exc_details[1])
|
|
||||||
pending_raise = True
|
|
||||||
exc_details = new_exc_details
|
|
||||||
if pending_raise:
|
|
||||||
try:
|
|
||||||
# bare "raise exc_details[1]" replaces our carefully
|
|
||||||
# set-up context
|
|
||||||
fixed_ctx = exc_details[1].__context__
|
|
||||||
raise exc_details[1]
|
|
||||||
except BaseException:
|
|
||||||
exc_details[1].__context__ = fixed_ctx
|
|
||||||
raise
|
|
||||||
return received_exc and suppressed_exc
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Immediately unwind the context stack."""
|
|
||||||
self.__exit__(None, None, None)
|
|
||||||
|
|
||||||
|
|
||||||
# Inspired by discussions on https://bugs.python.org/issue29302
|
|
||||||
class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
|
||||||
"""Async context manager for dynamic management of a stack of exit
|
|
||||||
callbacks.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
async with AsyncExitStack() as stack:
|
|
||||||
connections = [await stack.enter_async_context(get_connection())
|
|
||||||
for i in range(5)]
|
|
||||||
# All opened connections will automatically be released at the
|
|
||||||
# end of the async with statement, even if attempts to open a
|
|
||||||
# connection later in the list raise an exception.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_async_exit_wrapper(cm, cm_exit):
|
|
||||||
async def _exit_wrapper(exc_type, exc, tb):
|
|
||||||
return await cm_exit(cm, exc_type, exc, tb)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_async_cb_wrapper(callback, *args, **kwds):
|
|
||||||
async def _exit_wrapper(exc_type, exc, tb):
|
|
||||||
await callback(*args, **kwds)
|
|
||||||
return _exit_wrapper
|
|
||||||
|
|
||||||
async def enter_async_context(self, cm):
|
|
||||||
"""Enters the supplied async context manager.
|
|
||||||
|
|
||||||
If successful, also pushes its __aexit__ method as a callback and
|
|
||||||
returns the result of the __aenter__ method.
|
|
||||||
"""
|
|
||||||
_cm_type = type(cm)
|
|
||||||
_exit = _cm_type.__aexit__
|
|
||||||
result = await _cm_type.__aenter__(cm)
|
|
||||||
self._push_async_cm_exit(cm, _exit)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def push_async_exit(self, exit):
|
|
||||||
"""Registers a coroutine function with the standard __aexit__ method
|
|
||||||
signature.
|
|
||||||
|
|
||||||
Can suppress exceptions the same way __aexit__ method can.
|
|
||||||
Also accepts any object with an __aexit__ method (registering a call
|
|
||||||
to the method instead of the object itself).
|
|
||||||
"""
|
|
||||||
_cb_type = type(exit)
|
|
||||||
try:
|
|
||||||
exit_method = _cb_type.__aexit__
|
|
||||||
except AttributeError:
|
|
||||||
# Not an async context manager, so assume it's a coroutine function
|
|
||||||
self._push_exit_callback(exit, False)
|
|
||||||
else:
|
|
||||||
self._push_async_cm_exit(exit, exit_method)
|
|
||||||
return exit # Allow use as a decorator
|
|
||||||
|
|
||||||
def push_async_callback(self, callback, *args, **kwds):
|
|
||||||
"""Registers an arbitrary coroutine function and arguments.
|
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
|
||||||
"""
|
|
||||||
_exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
|
|
||||||
|
|
||||||
# We changed the signature, so using @wraps is not appropriate, but
|
|
||||||
# setting __wrapped__ may still help with introspection.
|
|
||||||
_exit_wrapper.__wrapped__ = callback
|
|
||||||
self._push_exit_callback(_exit_wrapper, False)
|
|
||||||
return callback # Allow use as a decorator
|
|
||||||
|
|
||||||
async def aclose(self):
|
|
||||||
"""Immediately unwind the context stack."""
|
|
||||||
await self.__aexit__(None, None, None)
|
|
||||||
|
|
||||||
def _push_async_cm_exit(self, cm, cm_exit):
|
|
||||||
"""Helper to correctly register coroutine function to __aexit__
|
|
||||||
method."""
|
|
||||||
_exit_wrapper = self._create_async_exit_wrapper(cm, cm_exit)
|
|
||||||
_exit_wrapper.__self__ = cm
|
|
||||||
self._push_exit_callback(_exit_wrapper, False)
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
async def __aexit__(self, *exc_details):
|
|
||||||
received_exc = exc_details[0] is not None
|
|
||||||
|
|
||||||
# We manipulate the exception state so it behaves as though
|
|
||||||
# we were actually nesting multiple with statements
|
|
||||||
frame_exc = sys.exc_info()[1]
|
|
||||||
def _fix_exception_context(new_exc, old_exc):
|
|
||||||
# Context may not be correct, so find the end of the chain
|
|
||||||
while 1:
|
|
||||||
exc_context = new_exc.__context__
|
|
||||||
if exc_context is old_exc:
|
|
||||||
# Context is already set correctly (see issue 20317)
|
|
||||||
return
|
|
||||||
if exc_context is None or exc_context is frame_exc:
|
|
||||||
break
|
|
||||||
new_exc = exc_context
|
|
||||||
# Change the end of the chain to point to the exception
|
|
||||||
# we expect it to reference
|
|
||||||
new_exc.__context__ = old_exc
|
|
||||||
|
|
||||||
# Callbacks are invoked in LIFO order to match the behaviour of
|
|
||||||
# nested context managers
|
|
||||||
suppressed_exc = False
|
|
||||||
pending_raise = False
|
|
||||||
while self._exit_callbacks:
|
|
||||||
is_sync, cb = self._exit_callbacks.pop()
|
|
||||||
try:
|
|
||||||
if is_sync:
|
|
||||||
cb_suppress = cb(*exc_details)
|
|
||||||
else:
|
|
||||||
cb_suppress = await cb(*exc_details)
|
|
||||||
|
|
||||||
if cb_suppress:
|
|
||||||
suppressed_exc = True
|
|
||||||
pending_raise = False
|
|
||||||
exc_details = (None, None, None)
|
|
||||||
except:
|
|
||||||
new_exc_details = sys.exc_info()
|
|
||||||
# simulate the stack of exceptions by setting the context
|
|
||||||
_fix_exception_context(new_exc_details[1], exc_details[1])
|
|
||||||
pending_raise = True
|
|
||||||
exc_details = new_exc_details
|
|
||||||
if pending_raise:
|
|
||||||
try:
|
|
||||||
# bare "raise exc_details[1]" replaces our carefully
|
|
||||||
# set-up context
|
|
||||||
fixed_ctx = exc_details[1].__context__
|
|
||||||
raise exc_details[1]
|
|
||||||
except BaseException:
|
|
||||||
exc_details[1].__context__ = fixed_ctx
|
|
||||||
raise
|
|
||||||
return received_exc and suppressed_exc
|
|
||||||
|
|
||||||
|
|
||||||
class nullcontext(AbstractContextManager):
|
|
||||||
"""Context manager that does no additional processing.
|
|
||||||
|
|
||||||
Used as a stand-in for a normal context manager, when a particular
|
|
||||||
block of code is only sometimes used with a normal context manager:
|
|
||||||
|
|
||||||
cm = optional_cm if condition else nullcontext()
|
|
||||||
with cm:
|
|
||||||
# Perform operation, using optional_cm if condition is True
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, enter_result=None):
|
|
||||||
self.enter_result = enter_result
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self.enter_result
|
|
||||||
|
|
||||||
def __exit__(self, *excinfo):
|
|
||||||
pass
|
|
||||||
313
Lib/copy.py
313
Lib/copy.py
@@ -1,313 +0,0 @@
|
|||||||
"""Generic (shallow and deep) copying operations.
|
|
||||||
|
|
||||||
Interface summary:
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
x = copy.copy(y) # make a shallow copy of y
|
|
||||||
x = copy.deepcopy(y) # make a deep copy of y
|
|
||||||
|
|
||||||
For module specific errors, copy.Error is raised.
|
|
||||||
|
|
||||||
The difference between shallow and deep copying is only relevant for
|
|
||||||
compound objects (objects that contain other objects, like lists or
|
|
||||||
class instances).
|
|
||||||
|
|
||||||
- A shallow copy constructs a new compound object and then (to the
|
|
||||||
extent possible) inserts *the same objects* into it that the
|
|
||||||
original contains.
|
|
||||||
|
|
||||||
- A deep copy constructs a new compound object and then, recursively,
|
|
||||||
inserts *copies* into it of the objects found in the original.
|
|
||||||
|
|
||||||
Two problems often exist with deep copy operations that don't exist
|
|
||||||
with shallow copy operations:
|
|
||||||
|
|
||||||
a) recursive objects (compound objects that, directly or indirectly,
|
|
||||||
contain a reference to themselves) may cause a recursive loop
|
|
||||||
|
|
||||||
b) because deep copy copies *everything* it may copy too much, e.g.
|
|
||||||
administrative data structures that should be shared even between
|
|
||||||
copies
|
|
||||||
|
|
||||||
Python's deep copy operation avoids these problems by:
|
|
||||||
|
|
||||||
a) keeping a table of objects already copied during the current
|
|
||||||
copying pass
|
|
||||||
|
|
||||||
b) letting user-defined classes override the copying operation or the
|
|
||||||
set of components copied
|
|
||||||
|
|
||||||
This version does not copy types like module, class, function, method,
|
|
||||||
nor stack trace, stack frame, nor file, socket, window, nor array, nor
|
|
||||||
any similar types.
|
|
||||||
|
|
||||||
Classes can use the same interfaces to control copying that they use
|
|
||||||
to control pickling: they can define methods called __getinitargs__(),
|
|
||||||
__getstate__() and __setstate__(). See the documentation for module
|
|
||||||
"pickle" for information on these methods.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import types
|
|
||||||
import weakref
|
|
||||||
from copyreg import dispatch_table
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
error = Error # backward compatibility
|
|
||||||
|
|
||||||
try:
|
|
||||||
from org.python.core import PyStringMap
|
|
||||||
except ImportError:
|
|
||||||
PyStringMap = None
|
|
||||||
|
|
||||||
__all__ = ["Error", "copy", "deepcopy"]
|
|
||||||
|
|
||||||
def copy(x):
|
|
||||||
"""Shallow copy operation on arbitrary Python objects.
|
|
||||||
|
|
||||||
See the module's __doc__ string for more info.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cls = type(x)
|
|
||||||
|
|
||||||
copier = _copy_dispatch.get(cls)
|
|
||||||
if copier:
|
|
||||||
return copier(x)
|
|
||||||
|
|
||||||
try:
|
|
||||||
issc = issubclass(cls, type)
|
|
||||||
except TypeError: # cls is not a class
|
|
||||||
issc = False
|
|
||||||
if issc:
|
|
||||||
# treat it as a regular class:
|
|
||||||
return _copy_immutable(x)
|
|
||||||
|
|
||||||
copier = getattr(cls, "__copy__", None)
|
|
||||||
if copier:
|
|
||||||
return copier(x)
|
|
||||||
|
|
||||||
reductor = dispatch_table.get(cls)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor(x)
|
|
||||||
else:
|
|
||||||
reductor = getattr(x, "__reduce_ex__", None)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor(4)
|
|
||||||
else:
|
|
||||||
reductor = getattr(x, "__reduce__", None)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor()
|
|
||||||
else:
|
|
||||||
raise Error("un(shallow)copyable object of type %s" % cls)
|
|
||||||
|
|
||||||
if isinstance(rv, str):
|
|
||||||
return x
|
|
||||||
return _reconstruct(x, None, *rv)
|
|
||||||
|
|
||||||
|
|
||||||
_copy_dispatch = d = {}
|
|
||||||
|
|
||||||
def _copy_immutable(x):
|
|
||||||
return x
|
|
||||||
for t in (type(None), int, float, bool, complex, str, tuple,
|
|
||||||
bytes, frozenset, type, range, slice,
|
|
||||||
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
|
|
||||||
types.FunctionType, weakref.ref):
|
|
||||||
d[t] = _copy_immutable
|
|
||||||
t = getattr(types, "CodeType", None)
|
|
||||||
if t is not None:
|
|
||||||
d[t] = _copy_immutable
|
|
||||||
|
|
||||||
d[list] = list.copy
|
|
||||||
d[dict] = dict.copy
|
|
||||||
d[set] = set.copy
|
|
||||||
d[bytearray] = bytearray.copy
|
|
||||||
|
|
||||||
if PyStringMap is not None:
|
|
||||||
d[PyStringMap] = PyStringMap.copy
|
|
||||||
|
|
||||||
del d, t
|
|
||||||
|
|
||||||
def deepcopy(x, memo=None, _nil=[]):
|
|
||||||
"""Deep copy operation on arbitrary Python objects.
|
|
||||||
|
|
||||||
See the module's __doc__ string for more info.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if memo is None:
|
|
||||||
memo = {}
|
|
||||||
|
|
||||||
d = id(x)
|
|
||||||
y = memo.get(d, _nil)
|
|
||||||
if y is not _nil:
|
|
||||||
return y
|
|
||||||
|
|
||||||
cls = type(x)
|
|
||||||
|
|
||||||
copier = _deepcopy_dispatch.get(cls)
|
|
||||||
if copier:
|
|
||||||
y = copier(x, memo)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
issc = issubclass(cls, type)
|
|
||||||
except TypeError: # cls is not a class (old Boost; see SF #502085)
|
|
||||||
issc = 0
|
|
||||||
if issc:
|
|
||||||
y = _deepcopy_atomic(x, memo)
|
|
||||||
else:
|
|
||||||
copier = getattr(x, "__deepcopy__", None)
|
|
||||||
if copier:
|
|
||||||
y = copier(memo)
|
|
||||||
else:
|
|
||||||
reductor = dispatch_table.get(cls)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor(x)
|
|
||||||
else:
|
|
||||||
reductor = getattr(x, "__reduce_ex__", None)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor(4)
|
|
||||||
else:
|
|
||||||
reductor = getattr(x, "__reduce__", None)
|
|
||||||
if reductor:
|
|
||||||
rv = reductor()
|
|
||||||
else:
|
|
||||||
raise Error(
|
|
||||||
"un(deep)copyable object of type %s" % cls)
|
|
||||||
if isinstance(rv, str):
|
|
||||||
y = x
|
|
||||||
else:
|
|
||||||
y = _reconstruct(x, memo, *rv)
|
|
||||||
|
|
||||||
# If is its own copy, don't memoize.
|
|
||||||
if y is not x:
|
|
||||||
memo[d] = y
|
|
||||||
_keep_alive(x, memo) # Make sure x lives at least as long as d
|
|
||||||
return y
|
|
||||||
|
|
||||||
_deepcopy_dispatch = d = {}
|
|
||||||
|
|
||||||
def _deepcopy_atomic(x, memo):
|
|
||||||
return x
|
|
||||||
d[type(None)] = _deepcopy_atomic
|
|
||||||
d[type(Ellipsis)] = _deepcopy_atomic
|
|
||||||
d[type(NotImplemented)] = _deepcopy_atomic
|
|
||||||
d[int] = _deepcopy_atomic
|
|
||||||
d[float] = _deepcopy_atomic
|
|
||||||
d[bool] = _deepcopy_atomic
|
|
||||||
d[complex] = _deepcopy_atomic
|
|
||||||
d[bytes] = _deepcopy_atomic
|
|
||||||
d[str] = _deepcopy_atomic
|
|
||||||
try:
|
|
||||||
d[types.CodeType] = _deepcopy_atomic
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
d[type] = _deepcopy_atomic
|
|
||||||
d[types.BuiltinFunctionType] = _deepcopy_atomic
|
|
||||||
d[types.FunctionType] = _deepcopy_atomic
|
|
||||||
d[weakref.ref] = _deepcopy_atomic
|
|
||||||
|
|
||||||
def _deepcopy_list(x, memo, deepcopy=deepcopy):
|
|
||||||
y = []
|
|
||||||
memo[id(x)] = y
|
|
||||||
append = y.append
|
|
||||||
for a in x:
|
|
||||||
append(deepcopy(a, memo))
|
|
||||||
return y
|
|
||||||
d[list] = _deepcopy_list
|
|
||||||
|
|
||||||
def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
|
|
||||||
y = [deepcopy(a, memo) for a in x]
|
|
||||||
# We're not going to put the tuple in the memo, but it's still important we
|
|
||||||
# check for it, in case the tuple contains recursive mutable structures.
|
|
||||||
try:
|
|
||||||
return memo[id(x)]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
for k, j in zip(x, y):
|
|
||||||
if k is not j:
|
|
||||||
y = tuple(y)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
y = x
|
|
||||||
return y
|
|
||||||
d[tuple] = _deepcopy_tuple
|
|
||||||
|
|
||||||
def _deepcopy_dict(x, memo, deepcopy=deepcopy):
|
|
||||||
y = {}
|
|
||||||
memo[id(x)] = y
|
|
||||||
for key, value in x.items():
|
|
||||||
y[deepcopy(key, memo)] = deepcopy(value, memo)
|
|
||||||
return y
|
|
||||||
d[dict] = _deepcopy_dict
|
|
||||||
if PyStringMap is not None:
|
|
||||||
d[PyStringMap] = _deepcopy_dict
|
|
||||||
|
|
||||||
def _deepcopy_method(x, memo): # Copy instance methods
|
|
||||||
return type(x)(x.__func__, deepcopy(x.__self__, memo))
|
|
||||||
d[types.MethodType] = _deepcopy_method
|
|
||||||
|
|
||||||
del d
|
|
||||||
|
|
||||||
def _keep_alive(x, memo):
|
|
||||||
"""Keeps a reference to the object x in the memo.
|
|
||||||
|
|
||||||
Because we remember objects by their id, we have
|
|
||||||
to assure that possibly temporary objects are kept
|
|
||||||
alive by referencing them.
|
|
||||||
We store a reference at the id of the memo, which should
|
|
||||||
normally not be used unless someone tries to deepcopy
|
|
||||||
the memo itself...
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
memo[id(memo)].append(x)
|
|
||||||
except KeyError:
|
|
||||||
# aha, this is the first one :-)
|
|
||||||
memo[id(memo)]=[x]
|
|
||||||
|
|
||||||
def _reconstruct(x, memo, func, args,
|
|
||||||
state=None, listiter=None, dictiter=None,
|
|
||||||
deepcopy=deepcopy):
|
|
||||||
deep = memo is not None
|
|
||||||
if deep and args:
|
|
||||||
args = (deepcopy(arg, memo) for arg in args)
|
|
||||||
y = func(*args)
|
|
||||||
if deep:
|
|
||||||
memo[id(x)] = y
|
|
||||||
|
|
||||||
if state is not None:
|
|
||||||
if deep:
|
|
||||||
state = deepcopy(state, memo)
|
|
||||||
if hasattr(y, '__setstate__'):
|
|
||||||
y.__setstate__(state)
|
|
||||||
else:
|
|
||||||
if isinstance(state, tuple) and len(state) == 2:
|
|
||||||
state, slotstate = state
|
|
||||||
else:
|
|
||||||
slotstate = None
|
|
||||||
if state is not None:
|
|
||||||
y.__dict__.update(state)
|
|
||||||
if slotstate is not None:
|
|
||||||
for key, value in slotstate.items():
|
|
||||||
setattr(y, key, value)
|
|
||||||
|
|
||||||
if listiter is not None:
|
|
||||||
if deep:
|
|
||||||
for item in listiter:
|
|
||||||
item = deepcopy(item, memo)
|
|
||||||
y.append(item)
|
|
||||||
else:
|
|
||||||
for item in listiter:
|
|
||||||
y.append(item)
|
|
||||||
if dictiter is not None:
|
|
||||||
if deep:
|
|
||||||
for key, value in dictiter:
|
|
||||||
key = deepcopy(key, memo)
|
|
||||||
value = deepcopy(value, memo)
|
|
||||||
y[key] = value
|
|
||||||
else:
|
|
||||||
for key, value in dictiter:
|
|
||||||
y[key] = value
|
|
||||||
return y
|
|
||||||
|
|
||||||
del types, weakref, PyStringMap
|
|
||||||
206
Lib/copyreg.py
206
Lib/copyreg.py
@@ -1,206 +0,0 @@
|
|||||||
"""Helper to provide extensibility for pickle.
|
|
||||||
|
|
||||||
This is only useful to add pickle support for extension types defined in
|
|
||||||
C, not for instances of user-defined classes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["pickle", "constructor",
|
|
||||||
"add_extension", "remove_extension", "clear_extension_cache"]
|
|
||||||
|
|
||||||
dispatch_table = {}
|
|
||||||
|
|
||||||
def pickle(ob_type, pickle_function, constructor_ob=None):
|
|
||||||
if not callable(pickle_function):
|
|
||||||
raise TypeError("reduction functions must be callable")
|
|
||||||
dispatch_table[ob_type] = pickle_function
|
|
||||||
|
|
||||||
# The constructor_ob function is a vestige of safe for unpickling.
|
|
||||||
# There is no reason for the caller to pass it anymore.
|
|
||||||
if constructor_ob is not None:
|
|
||||||
constructor(constructor_ob)
|
|
||||||
|
|
||||||
def constructor(object):
|
|
||||||
if not callable(object):
|
|
||||||
raise TypeError("constructors must be callable")
|
|
||||||
|
|
||||||
# Example: provide pickling support for complex numbers.
|
|
||||||
|
|
||||||
try:
|
|
||||||
complex
|
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
|
|
||||||
def pickle_complex(c):
|
|
||||||
return complex, (c.real, c.imag)
|
|
||||||
|
|
||||||
pickle(complex, pickle_complex, complex)
|
|
||||||
|
|
||||||
# Support for pickling new-style objects
|
|
||||||
|
|
||||||
def _reconstructor(cls, base, state):
|
|
||||||
if base is object:
|
|
||||||
obj = object.__new__(cls)
|
|
||||||
else:
|
|
||||||
obj = base.__new__(cls, state)
|
|
||||||
if base.__init__ != object.__init__:
|
|
||||||
base.__init__(obj, state)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
_HEAPTYPE = 1<<9
|
|
||||||
|
|
||||||
# Python code for object.__reduce_ex__ for protocols 0 and 1
|
|
||||||
|
|
||||||
def _reduce_ex(self, proto):
|
|
||||||
assert proto < 2
|
|
||||||
for base in self.__class__.__mro__:
|
|
||||||
if hasattr(base, '__flags__') and not base.__flags__ & _HEAPTYPE:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
base = object # not really reachable
|
|
||||||
if base is object:
|
|
||||||
state = None
|
|
||||||
else:
|
|
||||||
if base is self.__class__:
|
|
||||||
raise TypeError("can't pickle %s objects" % base.__name__)
|
|
||||||
state = base(self)
|
|
||||||
args = (self.__class__, base, state)
|
|
||||||
try:
|
|
||||||
getstate = self.__getstate__
|
|
||||||
except AttributeError:
|
|
||||||
if getattr(self, "__slots__", None):
|
|
||||||
raise TypeError("a class that defines __slots__ without "
|
|
||||||
"defining __getstate__ cannot be pickled")
|
|
||||||
try:
|
|
||||||
dict = self.__dict__
|
|
||||||
except AttributeError:
|
|
||||||
dict = None
|
|
||||||
else:
|
|
||||||
dict = getstate()
|
|
||||||
if dict:
|
|
||||||
return _reconstructor, args, dict
|
|
||||||
else:
|
|
||||||
return _reconstructor, args
|
|
||||||
|
|
||||||
# Helper for __reduce_ex__ protocol 2
|
|
||||||
|
|
||||||
def __newobj__(cls, *args):
|
|
||||||
return cls.__new__(cls, *args)
|
|
||||||
|
|
||||||
def __newobj_ex__(cls, args, kwargs):
|
|
||||||
"""Used by pickle protocol 4, instead of __newobj__ to allow classes with
|
|
||||||
keyword-only arguments to be pickled correctly.
|
|
||||||
"""
|
|
||||||
return cls.__new__(cls, *args, **kwargs)
|
|
||||||
|
|
||||||
def _slotnames(cls):
|
|
||||||
"""Return a list of slot names for a given class.
|
|
||||||
|
|
||||||
This needs to find slots defined by the class and its bases, so we
|
|
||||||
can't simply return the __slots__ attribute. We must walk down
|
|
||||||
the Method Resolution Order and concatenate the __slots__ of each
|
|
||||||
class found there. (This assumes classes don't modify their
|
|
||||||
__slots__ attribute to misrepresent their slots after the class is
|
|
||||||
defined.)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Get the value from a cache in the class if possible
|
|
||||||
names = cls.__dict__.get("__slotnames__")
|
|
||||||
if names is not None:
|
|
||||||
return names
|
|
||||||
|
|
||||||
# Not cached -- calculate the value
|
|
||||||
names = []
|
|
||||||
if not hasattr(cls, "__slots__"):
|
|
||||||
# This class has no slots
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Slots found -- gather slot names from all base classes
|
|
||||||
for c in cls.__mro__:
|
|
||||||
if "__slots__" in c.__dict__:
|
|
||||||
slots = c.__dict__['__slots__']
|
|
||||||
# if class has a single slot, it can be given as a string
|
|
||||||
if isinstance(slots, str):
|
|
||||||
slots = (slots,)
|
|
||||||
for name in slots:
|
|
||||||
# special descriptors
|
|
||||||
if name in ("__dict__", "__weakref__"):
|
|
||||||
continue
|
|
||||||
# mangled names
|
|
||||||
elif name.startswith('__') and not name.endswith('__'):
|
|
||||||
stripped = c.__name__.lstrip('_')
|
|
||||||
if stripped:
|
|
||||||
names.append('_%s%s' % (stripped, name))
|
|
||||||
else:
|
|
||||||
names.append(name)
|
|
||||||
else:
|
|
||||||
names.append(name)
|
|
||||||
|
|
||||||
# Cache the outcome in the class if at all possible
|
|
||||||
try:
|
|
||||||
cls.__slotnames__ = names
|
|
||||||
except:
|
|
||||||
pass # But don't die if we can't
|
|
||||||
|
|
||||||
return names
|
|
||||||
|
|
||||||
# A registry of extension codes. This is an ad-hoc compression
|
|
||||||
# mechanism. Whenever a global reference to <module>, <name> is about
|
|
||||||
# to be pickled, the (<module>, <name>) tuple is looked up here to see
|
|
||||||
# if it is a registered extension code for it. Extension codes are
|
|
||||||
# universal, so that the meaning of a pickle does not depend on
|
|
||||||
# context. (There are also some codes reserved for local use that
|
|
||||||
# don't have this restriction.) Codes are positive ints; 0 is
|
|
||||||
# reserved.
|
|
||||||
|
|
||||||
_extension_registry = {} # key -> code
|
|
||||||
_inverted_registry = {} # code -> key
|
|
||||||
_extension_cache = {} # code -> object
|
|
||||||
# Don't ever rebind those names: pickling grabs a reference to them when
|
|
||||||
# it's initialized, and won't see a rebinding.
|
|
||||||
|
|
||||||
def add_extension(module, name, code):
|
|
||||||
"""Register an extension code."""
|
|
||||||
code = int(code)
|
|
||||||
if not 1 <= code <= 0x7fffffff:
|
|
||||||
raise ValueError("code out of range")
|
|
||||||
key = (module, name)
|
|
||||||
if (_extension_registry.get(key) == code and
|
|
||||||
_inverted_registry.get(code) == key):
|
|
||||||
return # Redundant registrations are benign
|
|
||||||
if key in _extension_registry:
|
|
||||||
raise ValueError("key %s is already registered with code %s" %
|
|
||||||
(key, _extension_registry[key]))
|
|
||||||
if code in _inverted_registry:
|
|
||||||
raise ValueError("code %s is already in use for key %s" %
|
|
||||||
(code, _inverted_registry[code]))
|
|
||||||
_extension_registry[key] = code
|
|
||||||
_inverted_registry[code] = key
|
|
||||||
|
|
||||||
def remove_extension(module, name, code):
|
|
||||||
"""Unregister an extension code. For testing only."""
|
|
||||||
key = (module, name)
|
|
||||||
if (_extension_registry.get(key) != code or
|
|
||||||
_inverted_registry.get(code) != key):
|
|
||||||
raise ValueError("key %s is not registered with code %s" %
|
|
||||||
(key, code))
|
|
||||||
del _extension_registry[key]
|
|
||||||
del _inverted_registry[code]
|
|
||||||
if code in _extension_cache:
|
|
||||||
del _extension_cache[code]
|
|
||||||
|
|
||||||
def clear_extension_cache():
|
|
||||||
_extension_cache.clear()
|
|
||||||
|
|
||||||
# Standard extension code assignments
|
|
||||||
|
|
||||||
# Reserved ranges
|
|
||||||
|
|
||||||
# First Last Count Purpose
|
|
||||||
# 1 127 127 Reserved for Python standard library
|
|
||||||
# 128 191 64 Reserved for Zope
|
|
||||||
# 192 239 48 Reserved for 3rd parties
|
|
||||||
# 240 255 16 Reserved for private use (will never be assigned)
|
|
||||||
# 256 Inf Inf Reserved for future assignment
|
|
||||||
|
|
||||||
# Extension codes are assigned by the Python Software Foundation.
|
|
||||||
443
Lib/csv.py
443
Lib/csv.py
@@ -1,443 +0,0 @@
|
|||||||
|
|
||||||
"""
|
|
||||||
csv.py - read/write/investigate CSV files
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
from _csv import Error, reader, \
|
|
||||||
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE, \
|
|
||||||
__doc__
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
__all__ = ["QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
|
|
||||||
"Error", "Dialect", "__doc__", "excel", "excel_tab",
|
|
||||||
"field_size_limit", "reader", "writer",
|
|
||||||
"Sniffer",
|
|
||||||
"unregister_dialect", "__version__", "DictReader", "DictWriter",
|
|
||||||
"unix_dialect"]
|
|
||||||
|
|
||||||
class Dialect:
|
|
||||||
"""Describe a CSV dialect.
|
|
||||||
|
|
||||||
This must be subclassed (see csv.excel). Valid attributes are:
|
|
||||||
delimiter, quotechar, escapechar, doublequote, skipinitialspace,
|
|
||||||
lineterminator, quoting.
|
|
||||||
|
|
||||||
"""
|
|
||||||
_name = ""
|
|
||||||
_valid = False
|
|
||||||
# placeholders
|
|
||||||
delimiter = None
|
|
||||||
quotechar = None
|
|
||||||
escapechar = None
|
|
||||||
doublequote = None
|
|
||||||
skipinitialspace = None
|
|
||||||
lineterminator = None
|
|
||||||
quoting = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
if self.__class__ != Dialect:
|
|
||||||
self._valid = True
|
|
||||||
self._validate()
|
|
||||||
|
|
||||||
def _validate(self):
|
|
||||||
try:
|
|
||||||
_Dialect(self)
|
|
||||||
except TypeError as e:
|
|
||||||
# We do this for compatibility with py2.3
|
|
||||||
raise Error(str(e))
|
|
||||||
|
|
||||||
class excel(Dialect):
|
|
||||||
"""Describe the usual properties of Excel-generated CSV files."""
|
|
||||||
delimiter = ','
|
|
||||||
quotechar = '"'
|
|
||||||
doublequote = True
|
|
||||||
skipinitialspace = False
|
|
||||||
lineterminator = '\r\n'
|
|
||||||
quoting = QUOTE_MINIMAL
|
|
||||||
|
|
||||||
class excel_tab(excel):
|
|
||||||
"""Describe the usual properties of Excel-generated TAB-delimited files."""
|
|
||||||
delimiter = '\t'
|
|
||||||
|
|
||||||
class unix_dialect(Dialect):
|
|
||||||
"""Describe the usual properties of Unix-generated CSV files."""
|
|
||||||
delimiter = ','
|
|
||||||
quotechar = '"'
|
|
||||||
doublequote = True
|
|
||||||
skipinitialspace = False
|
|
||||||
lineterminator = '\n'
|
|
||||||
quoting = QUOTE_ALL
|
|
||||||
|
|
||||||
|
|
||||||
class DictReader:
|
|
||||||
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
|
|
||||||
dialect="excel", *args, **kwds):
|
|
||||||
self._fieldnames = fieldnames # list of keys for the dict
|
|
||||||
self.restkey = restkey # key to catch long rows
|
|
||||||
self.restval = restval # default value for short rows
|
|
||||||
self.reader = reader(f, dialect, *args, **kwds)
|
|
||||||
self.dialect = dialect
|
|
||||||
self.line_num = 0
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fieldnames(self):
|
|
||||||
if self._fieldnames is None:
|
|
||||||
try:
|
|
||||||
self._fieldnames = next(self.reader)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
self.line_num = self.reader.line_num
|
|
||||||
return self._fieldnames
|
|
||||||
|
|
||||||
@fieldnames.setter
|
|
||||||
def fieldnames(self, value):
|
|
||||||
self._fieldnames = value
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
if self.line_num == 0:
|
|
||||||
# Used only for its side effect.
|
|
||||||
self.fieldnames
|
|
||||||
row = next(self.reader)
|
|
||||||
self.line_num = self.reader.line_num
|
|
||||||
|
|
||||||
# unlike the basic reader, we prefer not to return blanks,
|
|
||||||
# because we will typically wind up with a dict full of None
|
|
||||||
# values
|
|
||||||
while row == []:
|
|
||||||
row = next(self.reader)
|
|
||||||
d = OrderedDict(zip(self.fieldnames, row))
|
|
||||||
lf = len(self.fieldnames)
|
|
||||||
lr = len(row)
|
|
||||||
if lf < lr:
|
|
||||||
d[self.restkey] = row[lf:]
|
|
||||||
elif lf > lr:
|
|
||||||
for key in self.fieldnames[lr:]:
|
|
||||||
d[key] = self.restval
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
class DictWriter:
|
|
||||||
def __init__(self, f, fieldnames, restval="", extrasaction="raise",
|
|
||||||
dialect="excel", *args, **kwds):
|
|
||||||
self.fieldnames = fieldnames # list of keys for the dict
|
|
||||||
self.restval = restval # for writing short dicts
|
|
||||||
if extrasaction.lower() not in ("raise", "ignore"):
|
|
||||||
raise ValueError("extrasaction (%s) must be 'raise' or 'ignore'"
|
|
||||||
% extrasaction)
|
|
||||||
self.extrasaction = extrasaction
|
|
||||||
self.writer = writer(f, dialect, *args, **kwds)
|
|
||||||
|
|
||||||
def writeheader(self):
|
|
||||||
header = dict(zip(self.fieldnames, self.fieldnames))
|
|
||||||
self.writerow(header)
|
|
||||||
|
|
||||||
def _dict_to_list(self, rowdict):
|
|
||||||
if self.extrasaction == "raise":
|
|
||||||
wrong_fields = rowdict.keys() - self.fieldnames
|
|
||||||
if wrong_fields:
|
|
||||||
raise ValueError("dict contains fields not in fieldnames: "
|
|
||||||
+ ", ".join([repr(x) for x in wrong_fields]))
|
|
||||||
return (rowdict.get(key, self.restval) for key in self.fieldnames)
|
|
||||||
|
|
||||||
def writerow(self, rowdict):
|
|
||||||
return self.writer.writerow(self._dict_to_list(rowdict))
|
|
||||||
|
|
||||||
def writerows(self, rowdicts):
|
|
||||||
return self.writer.writerows(map(self._dict_to_list, rowdicts))
|
|
||||||
|
|
||||||
# Guard Sniffer's type checking against builds that exclude complex()
|
|
||||||
try:
|
|
||||||
complex
|
|
||||||
except NameError:
|
|
||||||
complex = float
|
|
||||||
|
|
||||||
class Sniffer:
|
|
||||||
'''
|
|
||||||
"Sniffs" the format of a CSV file (i.e. delimiter, quotechar)
|
|
||||||
Returns a Dialect object.
|
|
||||||
'''
|
|
||||||
def __init__(self):
|
|
||||||
# in case there is more than one possible delimiter
|
|
||||||
self.preferred = [',', '\t', ';', ' ', ':']
|
|
||||||
|
|
||||||
|
|
||||||
def sniff(self, sample, delimiters=None):
|
|
||||||
"""
|
|
||||||
Returns a dialect (or None) corresponding to the sample
|
|
||||||
"""
|
|
||||||
|
|
||||||
quotechar, doublequote, delimiter, skipinitialspace = \
|
|
||||||
self._guess_quote_and_delimiter(sample, delimiters)
|
|
||||||
if not delimiter:
|
|
||||||
delimiter, skipinitialspace = self._guess_delimiter(sample,
|
|
||||||
delimiters)
|
|
||||||
|
|
||||||
if not delimiter:
|
|
||||||
raise Error("Could not determine delimiter")
|
|
||||||
|
|
||||||
class dialect(Dialect):
|
|
||||||
_name = "sniffed"
|
|
||||||
lineterminator = '\r\n'
|
|
||||||
quoting = QUOTE_MINIMAL
|
|
||||||
# escapechar = ''
|
|
||||||
|
|
||||||
dialect.doublequote = doublequote
|
|
||||||
dialect.delimiter = delimiter
|
|
||||||
# _csv.reader won't accept a quotechar of ''
|
|
||||||
dialect.quotechar = quotechar or '"'
|
|
||||||
dialect.skipinitialspace = skipinitialspace
|
|
||||||
|
|
||||||
return dialect
|
|
||||||
|
|
||||||
|
|
||||||
def _guess_quote_and_delimiter(self, data, delimiters):
|
|
||||||
"""
|
|
||||||
Looks for text enclosed between two identical quotes
|
|
||||||
(the probable quotechar) which are preceded and followed
|
|
||||||
by the same character (the probable delimiter).
|
|
||||||
For example:
|
|
||||||
,'some text',
|
|
||||||
The quote with the most wins, same with the delimiter.
|
|
||||||
If there is no quotechar the delimiter can't be determined
|
|
||||||
this way.
|
|
||||||
"""
|
|
||||||
|
|
||||||
matches = []
|
|
||||||
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
|
|
||||||
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
|
|
||||||
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\n)', # ,".*?"
|
|
||||||
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\n)'): # ".*?" (no delim, no space)
|
|
||||||
regexp = re.compile(restr, re.DOTALL | re.MULTILINE)
|
|
||||||
matches = regexp.findall(data)
|
|
||||||
if matches:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not matches:
|
|
||||||
# (quotechar, doublequote, delimiter, skipinitialspace)
|
|
||||||
return ('', False, None, 0)
|
|
||||||
quotes = {}
|
|
||||||
delims = {}
|
|
||||||
spaces = 0
|
|
||||||
groupindex = regexp.groupindex
|
|
||||||
for m in matches:
|
|
||||||
n = groupindex['quote'] - 1
|
|
||||||
key = m[n]
|
|
||||||
if key:
|
|
||||||
quotes[key] = quotes.get(key, 0) + 1
|
|
||||||
try:
|
|
||||||
n = groupindex['delim'] - 1
|
|
||||||
key = m[n]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
if key and (delimiters is None or key in delimiters):
|
|
||||||
delims[key] = delims.get(key, 0) + 1
|
|
||||||
try:
|
|
||||||
n = groupindex['space'] - 1
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
if m[n]:
|
|
||||||
spaces += 1
|
|
||||||
|
|
||||||
quotechar = max(quotes, key=quotes.get)
|
|
||||||
|
|
||||||
if delims:
|
|
||||||
delim = max(delims, key=delims.get)
|
|
||||||
skipinitialspace = delims[delim] == spaces
|
|
||||||
if delim == '\n': # most likely a file with a single column
|
|
||||||
delim = ''
|
|
||||||
else:
|
|
||||||
# there is *no* delimiter, it's a single column of quoted data
|
|
||||||
delim = ''
|
|
||||||
skipinitialspace = 0
|
|
||||||
|
|
||||||
# if we see an extra quote between delimiters, we've got a
|
|
||||||
# double quoted format
|
|
||||||
dq_regexp = re.compile(
|
|
||||||
r"((%(delim)s)|^)\W*%(quote)s[^%(delim)s\n]*%(quote)s[^%(delim)s\n]*%(quote)s\W*((%(delim)s)|$)" % \
|
|
||||||
{'delim':re.escape(delim), 'quote':quotechar}, re.MULTILINE)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if dq_regexp.search(data):
|
|
||||||
doublequote = True
|
|
||||||
else:
|
|
||||||
doublequote = False
|
|
||||||
|
|
||||||
return (quotechar, doublequote, delim, skipinitialspace)
|
|
||||||
|
|
||||||
|
|
||||||
def _guess_delimiter(self, data, delimiters):
|
|
||||||
"""
|
|
||||||
The delimiter /should/ occur the same number of times on
|
|
||||||
each row. However, due to malformed data, it may not. We don't want
|
|
||||||
an all or nothing approach, so we allow for small variations in this
|
|
||||||
number.
|
|
||||||
1) build a table of the frequency of each character on every line.
|
|
||||||
2) build a table of frequencies of this frequency (meta-frequency?),
|
|
||||||
e.g. 'x occurred 5 times in 10 rows, 6 times in 1000 rows,
|
|
||||||
7 times in 2 rows'
|
|
||||||
3) use the mode of the meta-frequency to determine the /expected/
|
|
||||||
frequency for that character
|
|
||||||
4) find out how often the character actually meets that goal
|
|
||||||
5) the character that best meets its goal is the delimiter
|
|
||||||
For performance reasons, the data is evaluated in chunks, so it can
|
|
||||||
try and evaluate the smallest portion of the data possible, evaluating
|
|
||||||
additional chunks as necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = list(filter(None, data.split('\n')))
|
|
||||||
|
|
||||||
ascii = [chr(c) for c in range(127)] # 7-bit ASCII
|
|
||||||
|
|
||||||
# build frequency tables
|
|
||||||
chunkLength = min(10, len(data))
|
|
||||||
iteration = 0
|
|
||||||
charFrequency = {}
|
|
||||||
modes = {}
|
|
||||||
delims = {}
|
|
||||||
start, end = 0, chunkLength
|
|
||||||
while start < len(data):
|
|
||||||
iteration += 1
|
|
||||||
for line in data[start:end]:
|
|
||||||
for char in ascii:
|
|
||||||
metaFrequency = charFrequency.get(char, {})
|
|
||||||
# must count even if frequency is 0
|
|
||||||
freq = line.count(char)
|
|
||||||
# value is the mode
|
|
||||||
metaFrequency[freq] = metaFrequency.get(freq, 0) + 1
|
|
||||||
charFrequency[char] = metaFrequency
|
|
||||||
|
|
||||||
for char in charFrequency.keys():
|
|
||||||
items = list(charFrequency[char].items())
|
|
||||||
if len(items) == 1 and items[0][0] == 0:
|
|
||||||
continue
|
|
||||||
# get the mode of the frequencies
|
|
||||||
if len(items) > 1:
|
|
||||||
modes[char] = max(items, key=lambda x: x[1])
|
|
||||||
# adjust the mode - subtract the sum of all
|
|
||||||
# other frequencies
|
|
||||||
items.remove(modes[char])
|
|
||||||
modes[char] = (modes[char][0], modes[char][1]
|
|
||||||
- sum(item[1] for item in items))
|
|
||||||
else:
|
|
||||||
modes[char] = items[0]
|
|
||||||
|
|
||||||
# build a list of possible delimiters
|
|
||||||
modeList = modes.items()
|
|
||||||
total = float(min(chunkLength * iteration, len(data)))
|
|
||||||
# (rows of consistent data) / (number of rows) = 100%
|
|
||||||
consistency = 1.0
|
|
||||||
# minimum consistency threshold
|
|
||||||
threshold = 0.9
|
|
||||||
while len(delims) == 0 and consistency >= threshold:
|
|
||||||
for k, v in modeList:
|
|
||||||
if v[0] > 0 and v[1] > 0:
|
|
||||||
if ((v[1]/total) >= consistency and
|
|
||||||
(delimiters is None or k in delimiters)):
|
|
||||||
delims[k] = v
|
|
||||||
consistency -= 0.01
|
|
||||||
|
|
||||||
if len(delims) == 1:
|
|
||||||
delim = list(delims.keys())[0]
|
|
||||||
skipinitialspace = (data[0].count(delim) ==
|
|
||||||
data[0].count("%c " % delim))
|
|
||||||
return (delim, skipinitialspace)
|
|
||||||
|
|
||||||
# analyze another chunkLength lines
|
|
||||||
start = end
|
|
||||||
end += chunkLength
|
|
||||||
|
|
||||||
if not delims:
|
|
||||||
return ('', 0)
|
|
||||||
|
|
||||||
# if there's more than one, fall back to a 'preferred' list
|
|
||||||
if len(delims) > 1:
|
|
||||||
for d in self.preferred:
|
|
||||||
if d in delims.keys():
|
|
||||||
skipinitialspace = (data[0].count(d) ==
|
|
||||||
data[0].count("%c " % d))
|
|
||||||
return (d, skipinitialspace)
|
|
||||||
|
|
||||||
# nothing else indicates a preference, pick the character that
|
|
||||||
# dominates(?)
|
|
||||||
items = [(v,k) for (k,v) in delims.items()]
|
|
||||||
items.sort()
|
|
||||||
delim = items[-1][1]
|
|
||||||
|
|
||||||
skipinitialspace = (data[0].count(delim) ==
|
|
||||||
data[0].count("%c " % delim))
|
|
||||||
return (delim, skipinitialspace)
|
|
||||||
|
|
||||||
|
|
||||||
def has_header(self, sample):
|
|
||||||
# Creates a dictionary of types of data in each column. If any
|
|
||||||
# column is of a single type (say, integers), *except* for the first
|
|
||||||
# row, then the first row is presumed to be labels. If the type
|
|
||||||
# can't be determined, it is assumed to be a string in which case
|
|
||||||
# the length of the string is the determining factor: if all of the
|
|
||||||
# rows except for the first are the same length, it's a header.
|
|
||||||
# Finally, a 'vote' is taken at the end for each column, adding or
|
|
||||||
# subtracting from the likelihood of the first row being a header.
|
|
||||||
|
|
||||||
rdr = reader(StringIO(sample), self.sniff(sample))
|
|
||||||
|
|
||||||
header = next(rdr) # assume first row is header
|
|
||||||
|
|
||||||
columns = len(header)
|
|
||||||
columnTypes = {}
|
|
||||||
for i in range(columns): columnTypes[i] = None
|
|
||||||
|
|
||||||
checked = 0
|
|
||||||
for row in rdr:
|
|
||||||
# arbitrary number of rows to check, to keep it sane
|
|
||||||
if checked > 20:
|
|
||||||
break
|
|
||||||
checked += 1
|
|
||||||
|
|
||||||
if len(row) != columns:
|
|
||||||
continue # skip rows that have irregular number of columns
|
|
||||||
|
|
||||||
for col in list(columnTypes.keys()):
|
|
||||||
|
|
||||||
for thisType in [int, float, complex]:
|
|
||||||
try:
|
|
||||||
thisType(row[col])
|
|
||||||
break
|
|
||||||
except (ValueError, OverflowError):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# fallback to length of string
|
|
||||||
thisType = len(row[col])
|
|
||||||
|
|
||||||
if thisType != columnTypes[col]:
|
|
||||||
if columnTypes[col] is None: # add new column type
|
|
||||||
columnTypes[col] = thisType
|
|
||||||
else:
|
|
||||||
# type is inconsistent, remove column from
|
|
||||||
# consideration
|
|
||||||
del columnTypes[col]
|
|
||||||
|
|
||||||
# finally, compare results against first row and "vote"
|
|
||||||
# on whether it's a header
|
|
||||||
hasHeader = 0
|
|
||||||
for col, colType in columnTypes.items():
|
|
||||||
if type(colType) == type(0): # it's a length
|
|
||||||
if len(header[col]) != colType:
|
|
||||||
hasHeader += 1
|
|
||||||
else:
|
|
||||||
hasHeader -= 1
|
|
||||||
else: # attempt typecast
|
|
||||||
try:
|
|
||||||
colType(header[col])
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
hasHeader += 1
|
|
||||||
else:
|
|
||||||
hasHeader -= 1
|
|
||||||
|
|
||||||
return hasHeader > 0
|
|
||||||
2472
Lib/datetime.py
2472
Lib/datetime.py
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
try:
|
|
||||||
from _decimal import *
|
|
||||||
from _decimal import __doc__
|
|
||||||
from _decimal import __version__
|
|
||||||
from _decimal import __libmpdec_version__
|
|
||||||
except ImportError:
|
|
||||||
from _pydecimal import *
|
|
||||||
from _pydecimal import __doc__
|
|
||||||
from _pydecimal import __version__
|
|
||||||
from _pydecimal import __libmpdec_version__
|
|
||||||
2097
Lib/difflib.py
2097
Lib/difflib.py
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
This directory contains the Distutils package.
|
|
||||||
|
|
||||||
There's a full documentation available at:
|
|
||||||
|
|
||||||
http://docs.python.org/distutils/
|
|
||||||
|
|
||||||
The Distutils-SIG web page is also a good starting point:
|
|
||||||
|
|
||||||
http://www.python.org/sigs/distutils-sig/
|
|
||||||
|
|
||||||
WARNING : Distutils must remain compatible with 2.3
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
"""distutils
|
|
||||||
|
|
||||||
The main package for the Python Module Distribution Utilities. Normally
|
|
||||||
used from a setup script as
|
|
||||||
|
|
||||||
from distutils.core import setup
|
|
||||||
|
|
||||||
setup (...)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
__version__ = sys.version[:sys.version.index(' ')]
|
|
||||||
@@ -1,574 +0,0 @@
|
|||||||
"""distutils._msvccompiler
|
|
||||||
|
|
||||||
Contains MSVCCompiler, an implementation of the abstract CCompiler class
|
|
||||||
for Microsoft Visual Studio 2015.
|
|
||||||
|
|
||||||
The module is compatible with VS 2015 and later. You can find legacy support
|
|
||||||
for older versions in distutils.msvc9compiler and distutils.msvccompiler.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Written by Perry Stoll
|
|
||||||
# hacked by Robin Becker and Thomas Heller to do a better job of
|
|
||||||
# finding DevStudio (through the registry)
|
|
||||||
# ported to VS 2005 and VS 2008 by Christian Heimes
|
|
||||||
# ported to VS 2015 by Steve Dower
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
import winreg
|
|
||||||
|
|
||||||
from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
|
|
||||||
CompileError, LibError, LinkError
|
|
||||||
from distutils.ccompiler import CCompiler, gen_lib_options
|
|
||||||
from distutils import log
|
|
||||||
from distutils.util import get_platform
|
|
||||||
|
|
||||||
from itertools import count
|
|
||||||
|
|
||||||
def _find_vc2015():
|
|
||||||
try:
|
|
||||||
key = winreg.OpenKeyEx(
|
|
||||||
winreg.HKEY_LOCAL_MACHINE,
|
|
||||||
r"Software\Microsoft\VisualStudio\SxS\VC7",
|
|
||||||
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
|
|
||||||
)
|
|
||||||
except OSError:
|
|
||||||
log.debug("Visual C++ is not registered")
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
best_version = 0
|
|
||||||
best_dir = None
|
|
||||||
with key:
|
|
||||||
for i in count():
|
|
||||||
try:
|
|
||||||
v, vc_dir, vt = winreg.EnumValue(key, i)
|
|
||||||
except OSError:
|
|
||||||
break
|
|
||||||
if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
|
|
||||||
try:
|
|
||||||
version = int(float(v))
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
continue
|
|
||||||
if version >= 14 and version > best_version:
|
|
||||||
best_version, best_dir = version, vc_dir
|
|
||||||
return best_version, best_dir
|
|
||||||
|
|
||||||
def _find_vc2017():
|
|
||||||
import _distutils_findvs
|
|
||||||
import threading
|
|
||||||
|
|
||||||
best_version = 0, # tuple for full version comparisons
|
|
||||||
best_dir = None
|
|
||||||
|
|
||||||
# We need to call findall() on its own thread because it will
|
|
||||||
# initialize COM.
|
|
||||||
all_packages = []
|
|
||||||
def _getall():
|
|
||||||
all_packages.extend(_distutils_findvs.findall())
|
|
||||||
t = threading.Thread(target=_getall)
|
|
||||||
t.start()
|
|
||||||
t.join()
|
|
||||||
|
|
||||||
for name, version_str, path, packages in all_packages:
|
|
||||||
if 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' in packages:
|
|
||||||
vc_dir = os.path.join(path, 'VC', 'Auxiliary', 'Build')
|
|
||||||
if not os.path.isdir(vc_dir):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
version = tuple(int(i) for i in version_str.split('.'))
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
continue
|
|
||||||
if version > best_version:
|
|
||||||
best_version, best_dir = version, vc_dir
|
|
||||||
try:
|
|
||||||
best_version = best_version[0]
|
|
||||||
except IndexError:
|
|
||||||
best_version = None
|
|
||||||
return best_version, best_dir
|
|
||||||
|
|
||||||
def _find_vcvarsall(plat_spec):
|
|
||||||
best_version, best_dir = _find_vc2017()
|
|
||||||
vcruntime = None
|
|
||||||
vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
|
|
||||||
if best_version:
|
|
||||||
vcredist = os.path.join(best_dir, "..", "..", "redist", "MSVC", "**",
|
|
||||||
"Microsoft.VC141.CRT", "vcruntime140.dll")
|
|
||||||
try:
|
|
||||||
import glob
|
|
||||||
vcruntime = glob.glob(vcredist, recursive=True)[-1]
|
|
||||||
except (ImportError, OSError, LookupError):
|
|
||||||
vcruntime = None
|
|
||||||
|
|
||||||
if not best_version:
|
|
||||||
best_version, best_dir = _find_vc2015()
|
|
||||||
if best_version:
|
|
||||||
vcruntime = os.path.join(best_dir, 'redist', vcruntime_plat,
|
|
||||||
"Microsoft.VC140.CRT", "vcruntime140.dll")
|
|
||||||
|
|
||||||
if not best_version:
|
|
||||||
log.debug("No suitable Visual C++ version found")
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
|
|
||||||
if not os.path.isfile(vcvarsall):
|
|
||||||
log.debug("%s cannot be found", vcvarsall)
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
if not vcruntime or not os.path.isfile(vcruntime):
|
|
||||||
log.debug("%s cannot be found", vcruntime)
|
|
||||||
vcruntime = None
|
|
||||||
|
|
||||||
return vcvarsall, vcruntime
|
|
||||||
|
|
||||||
def _get_vc_env(plat_spec):
|
|
||||||
if os.getenv("DISTUTILS_USE_SDK"):
|
|
||||||
return {
|
|
||||||
key.lower(): value
|
|
||||||
for key, value in os.environ.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
vcvarsall, vcruntime = _find_vcvarsall(plat_spec)
|
|
||||||
if not vcvarsall:
|
|
||||||
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
|
|
||||||
|
|
||||||
try:
|
|
||||||
out = subprocess.check_output(
|
|
||||||
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
).decode('utf-16le', errors='replace')
|
|
||||||
except subprocess.CalledProcessError as exc:
|
|
||||||
log.error(exc.output)
|
|
||||||
raise DistutilsPlatformError("Error executing {}"
|
|
||||||
.format(exc.cmd))
|
|
||||||
|
|
||||||
env = {
|
|
||||||
key.lower(): value
|
|
||||||
for key, _, value in
|
|
||||||
(line.partition('=') for line in out.splitlines())
|
|
||||||
if key and value
|
|
||||||
}
|
|
||||||
|
|
||||||
if vcruntime:
|
|
||||||
env['py_vcruntime_redist'] = vcruntime
|
|
||||||
return env
|
|
||||||
|
|
||||||
def _find_exe(exe, paths=None):
|
|
||||||
"""Return path to an MSVC executable program.
|
|
||||||
|
|
||||||
Tries to find the program in several places: first, one of the
|
|
||||||
MSVC program search paths from the registry; next, the directories
|
|
||||||
in the PATH environment variable. If any of those work, return an
|
|
||||||
absolute path that is known to exist. If none of them work, just
|
|
||||||
return the original program name, 'exe'.
|
|
||||||
"""
|
|
||||||
if not paths:
|
|
||||||
paths = os.getenv('path').split(os.pathsep)
|
|
||||||
for p in paths:
|
|
||||||
fn = os.path.join(os.path.abspath(p), exe)
|
|
||||||
if os.path.isfile(fn):
|
|
||||||
return fn
|
|
||||||
return exe
|
|
||||||
|
|
||||||
# A map keyed by get_platform() return values to values accepted by
|
|
||||||
# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
|
|
||||||
# lighter-weight MSVC installs that do not include native 64-bit tools.
|
|
||||||
PLAT_TO_VCVARS = {
|
|
||||||
'win32' : 'x86',
|
|
||||||
'win-amd64' : 'x86_amd64',
|
|
||||||
}
|
|
||||||
|
|
||||||
# A set containing the DLLs that are guaranteed to be available for
|
|
||||||
# all micro versions of this Python version. Known extension
|
|
||||||
# dependencies that are not in this set will be copied to the output
|
|
||||||
# path.
|
|
||||||
_BUNDLED_DLLS = frozenset(['vcruntime140.dll'])
|
|
||||||
|
|
||||||
class MSVCCompiler(CCompiler) :
|
|
||||||
"""Concrete class that implements an interface to Microsoft Visual C++,
|
|
||||||
as defined by the CCompiler abstract class."""
|
|
||||||
|
|
||||||
compiler_type = 'msvc'
|
|
||||||
|
|
||||||
# Just set this so CCompiler's constructor doesn't barf. We currently
|
|
||||||
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
|
|
||||||
# as it really isn't necessary for this sort of single-compiler class.
|
|
||||||
# Would be nice to have a consistent interface with UnixCCompiler,
|
|
||||||
# though, so it's worth thinking about.
|
|
||||||
executables = {}
|
|
||||||
|
|
||||||
# Private class data (need to distinguish C from C++ source for compiler)
|
|
||||||
_c_extensions = ['.c']
|
|
||||||
_cpp_extensions = ['.cc', '.cpp', '.cxx']
|
|
||||||
_rc_extensions = ['.rc']
|
|
||||||
_mc_extensions = ['.mc']
|
|
||||||
|
|
||||||
# Needed for the filename generation methods provided by the
|
|
||||||
# base class, CCompiler.
|
|
||||||
src_extensions = (_c_extensions + _cpp_extensions +
|
|
||||||
_rc_extensions + _mc_extensions)
|
|
||||||
res_extension = '.res'
|
|
||||||
obj_extension = '.obj'
|
|
||||||
static_lib_extension = '.lib'
|
|
||||||
shared_lib_extension = '.dll'
|
|
||||||
static_lib_format = shared_lib_format = '%s%s'
|
|
||||||
exe_extension = '.exe'
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
|
||||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
|
||||||
# target platform (.plat_name is consistent with 'bdist')
|
|
||||||
self.plat_name = None
|
|
||||||
self.initialized = False
|
|
||||||
|
|
||||||
def initialize(self, plat_name=None):
|
|
||||||
# multi-init means we would need to check platform same each time...
|
|
||||||
assert not self.initialized, "don't init multiple times"
|
|
||||||
if plat_name is None:
|
|
||||||
plat_name = get_platform()
|
|
||||||
# sanity check for platforms to prevent obscure errors later.
|
|
||||||
if plat_name not in PLAT_TO_VCVARS:
|
|
||||||
raise DistutilsPlatformError("--plat-name must be one of {}"
|
|
||||||
.format(tuple(PLAT_TO_VCVARS)))
|
|
||||||
|
|
||||||
# Get the vcvarsall.bat spec for the requested platform.
|
|
||||||
plat_spec = PLAT_TO_VCVARS[plat_name]
|
|
||||||
|
|
||||||
vc_env = _get_vc_env(plat_spec)
|
|
||||||
if not vc_env:
|
|
||||||
raise DistutilsPlatformError("Unable to find a compatible "
|
|
||||||
"Visual Studio installation.")
|
|
||||||
|
|
||||||
self._paths = vc_env.get('path', '')
|
|
||||||
paths = self._paths.split(os.pathsep)
|
|
||||||
self.cc = _find_exe("cl.exe", paths)
|
|
||||||
self.linker = _find_exe("link.exe", paths)
|
|
||||||
self.lib = _find_exe("lib.exe", paths)
|
|
||||||
self.rc = _find_exe("rc.exe", paths) # resource compiler
|
|
||||||
self.mc = _find_exe("mc.exe", paths) # message compiler
|
|
||||||
self.mt = _find_exe("mt.exe", paths) # message compiler
|
|
||||||
self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '')
|
|
||||||
|
|
||||||
for dir in vc_env.get('include', '').split(os.pathsep):
|
|
||||||
if dir:
|
|
||||||
self.add_include_dir(dir.rstrip(os.sep))
|
|
||||||
|
|
||||||
for dir in vc_env.get('lib', '').split(os.pathsep):
|
|
||||||
if dir:
|
|
||||||
self.add_library_dir(dir.rstrip(os.sep))
|
|
||||||
|
|
||||||
self.preprocess_options = None
|
|
||||||
# If vcruntime_redist is available, link against it dynamically. Otherwise,
|
|
||||||
# use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib
|
|
||||||
# later to dynamically link to ucrtbase but not vcruntime.
|
|
||||||
self.compile_options = [
|
|
||||||
'/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG'
|
|
||||||
]
|
|
||||||
self.compile_options.append('/MD' if self._vcruntime_redist else '/MT')
|
|
||||||
|
|
||||||
self.compile_options_debug = [
|
|
||||||
'/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
|
|
||||||
]
|
|
||||||
|
|
||||||
ldflags = [
|
|
||||||
'/nologo', '/INCREMENTAL:NO', '/LTCG'
|
|
||||||
]
|
|
||||||
if not self._vcruntime_redist:
|
|
||||||
ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib'))
|
|
||||||
|
|
||||||
ldflags_debug = [
|
|
||||||
'/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'
|
|
||||||
]
|
|
||||||
|
|
||||||
self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']
|
|
||||||
self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']
|
|
||||||
self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
|
||||||
self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
|
||||||
self.ldflags_static = [*ldflags]
|
|
||||||
self.ldflags_static_debug = [*ldflags_debug]
|
|
||||||
|
|
||||||
self._ldflags = {
|
|
||||||
(CCompiler.EXECUTABLE, None): self.ldflags_exe,
|
|
||||||
(CCompiler.EXECUTABLE, False): self.ldflags_exe,
|
|
||||||
(CCompiler.EXECUTABLE, True): self.ldflags_exe_debug,
|
|
||||||
(CCompiler.SHARED_OBJECT, None): self.ldflags_shared,
|
|
||||||
(CCompiler.SHARED_OBJECT, False): self.ldflags_shared,
|
|
||||||
(CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug,
|
|
||||||
(CCompiler.SHARED_LIBRARY, None): self.ldflags_static,
|
|
||||||
(CCompiler.SHARED_LIBRARY, False): self.ldflags_static,
|
|
||||||
(CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.initialized = True
|
|
||||||
|
|
||||||
# -- Worker methods ------------------------------------------------
|
|
||||||
|
|
||||||
def object_filenames(self,
|
|
||||||
source_filenames,
|
|
||||||
strip_dir=0,
|
|
||||||
output_dir=''):
|
|
||||||
ext_map = {
|
|
||||||
**{ext: self.obj_extension for ext in self.src_extensions},
|
|
||||||
**{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},
|
|
||||||
}
|
|
||||||
|
|
||||||
output_dir = output_dir or ''
|
|
||||||
|
|
||||||
def make_out_path(p):
|
|
||||||
base, ext = os.path.splitext(p)
|
|
||||||
if strip_dir:
|
|
||||||
base = os.path.basename(base)
|
|
||||||
else:
|
|
||||||
_, base = os.path.splitdrive(base)
|
|
||||||
if base.startswith((os.path.sep, os.path.altsep)):
|
|
||||||
base = base[1:]
|
|
||||||
try:
|
|
||||||
# XXX: This may produce absurdly long paths. We should check
|
|
||||||
# the length of the result and trim base until we fit within
|
|
||||||
# 260 characters.
|
|
||||||
return os.path.join(output_dir, base + ext_map[ext])
|
|
||||||
except LookupError:
|
|
||||||
# Better to raise an exception instead of silently continuing
|
|
||||||
# and later complain about sources and targets having
|
|
||||||
# different lengths
|
|
||||||
raise CompileError("Don't know how to compile {}".format(p))
|
|
||||||
|
|
||||||
return list(map(make_out_path, source_filenames))
|
|
||||||
|
|
||||||
|
|
||||||
def compile(self, sources,
|
|
||||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
|
||||||
extra_preargs=None, extra_postargs=None, depends=None):
|
|
||||||
|
|
||||||
if not self.initialized:
|
|
||||||
self.initialize()
|
|
||||||
compile_info = self._setup_compile(output_dir, macros, include_dirs,
|
|
||||||
sources, depends, extra_postargs)
|
|
||||||
macros, objects, extra_postargs, pp_opts, build = compile_info
|
|
||||||
|
|
||||||
compile_opts = extra_preargs or []
|
|
||||||
compile_opts.append('/c')
|
|
||||||
if debug:
|
|
||||||
compile_opts.extend(self.compile_options_debug)
|
|
||||||
else:
|
|
||||||
compile_opts.extend(self.compile_options)
|
|
||||||
|
|
||||||
|
|
||||||
add_cpp_opts = False
|
|
||||||
|
|
||||||
for obj in objects:
|
|
||||||
try:
|
|
||||||
src, ext = build[obj]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
if debug:
|
|
||||||
# pass the full pathname to MSVC in debug mode,
|
|
||||||
# this allows the debugger to find the source file
|
|
||||||
# without asking the user to browse for it
|
|
||||||
src = os.path.abspath(src)
|
|
||||||
|
|
||||||
if ext in self._c_extensions:
|
|
||||||
input_opt = "/Tc" + src
|
|
||||||
elif ext in self._cpp_extensions:
|
|
||||||
input_opt = "/Tp" + src
|
|
||||||
add_cpp_opts = True
|
|
||||||
elif ext in self._rc_extensions:
|
|
||||||
# compile .RC to .RES file
|
|
||||||
input_opt = src
|
|
||||||
output_opt = "/fo" + obj
|
|
||||||
try:
|
|
||||||
self.spawn([self.rc] + pp_opts + [output_opt, input_opt])
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise CompileError(msg)
|
|
||||||
continue
|
|
||||||
elif ext in self._mc_extensions:
|
|
||||||
# Compile .MC to .RC file to .RES file.
|
|
||||||
# * '-h dir' specifies the directory for the
|
|
||||||
# generated include file
|
|
||||||
# * '-r dir' specifies the target directory of the
|
|
||||||
# generated RC file and the binary message resource
|
|
||||||
# it includes
|
|
||||||
#
|
|
||||||
# For now (since there are no options to change this),
|
|
||||||
# we use the source-directory for the include file and
|
|
||||||
# the build directory for the RC file and message
|
|
||||||
# resources. This works at least for win32all.
|
|
||||||
h_dir = os.path.dirname(src)
|
|
||||||
rc_dir = os.path.dirname(obj)
|
|
||||||
try:
|
|
||||||
# first compile .MC to .RC and .H file
|
|
||||||
self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
|
|
||||||
base, _ = os.path.splitext(os.path.basename (src))
|
|
||||||
rc_file = os.path.join(rc_dir, base + '.rc')
|
|
||||||
# then compile .RC to .RES file
|
|
||||||
self.spawn([self.rc, "/fo" + obj, rc_file])
|
|
||||||
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise CompileError(msg)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# how to handle this file?
|
|
||||||
raise CompileError("Don't know how to compile {} to {}"
|
|
||||||
.format(src, obj))
|
|
||||||
|
|
||||||
args = [self.cc] + compile_opts + pp_opts
|
|
||||||
if add_cpp_opts:
|
|
||||||
args.append('/EHsc')
|
|
||||||
args.append(input_opt)
|
|
||||||
args.append("/Fo" + obj)
|
|
||||||
args.extend(extra_postargs)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.spawn(args)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise CompileError(msg)
|
|
||||||
|
|
||||||
return objects
|
|
||||||
|
|
||||||
|
|
||||||
def create_static_lib(self,
|
|
||||||
objects,
|
|
||||||
output_libname,
|
|
||||||
output_dir=None,
|
|
||||||
debug=0,
|
|
||||||
target_lang=None):
|
|
||||||
|
|
||||||
if not self.initialized:
|
|
||||||
self.initialize()
|
|
||||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
|
||||||
output_filename = self.library_filename(output_libname,
|
|
||||||
output_dir=output_dir)
|
|
||||||
|
|
||||||
if self._need_link(objects, output_filename):
|
|
||||||
lib_args = objects + ['/OUT:' + output_filename]
|
|
||||||
if debug:
|
|
||||||
pass # XXX what goes here?
|
|
||||||
try:
|
|
||||||
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
|
|
||||||
self.spawn([self.lib] + lib_args)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise LibError(msg)
|
|
||||||
else:
|
|
||||||
log.debug("skipping %s (up-to-date)", output_filename)
|
|
||||||
|
|
||||||
|
|
||||||
def link(self,
|
|
||||||
target_desc,
|
|
||||||
objects,
|
|
||||||
output_filename,
|
|
||||||
output_dir=None,
|
|
||||||
libraries=None,
|
|
||||||
library_dirs=None,
|
|
||||||
runtime_library_dirs=None,
|
|
||||||
export_symbols=None,
|
|
||||||
debug=0,
|
|
||||||
extra_preargs=None,
|
|
||||||
extra_postargs=None,
|
|
||||||
build_temp=None,
|
|
||||||
target_lang=None):
|
|
||||||
|
|
||||||
if not self.initialized:
|
|
||||||
self.initialize()
|
|
||||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
|
||||||
fixed_args = self._fix_lib_args(libraries, library_dirs,
|
|
||||||
runtime_library_dirs)
|
|
||||||
libraries, library_dirs, runtime_library_dirs = fixed_args
|
|
||||||
|
|
||||||
if runtime_library_dirs:
|
|
||||||
self.warn("I don't know what to do with 'runtime_library_dirs': "
|
|
||||||
+ str(runtime_library_dirs))
|
|
||||||
|
|
||||||
lib_opts = gen_lib_options(self,
|
|
||||||
library_dirs, runtime_library_dirs,
|
|
||||||
libraries)
|
|
||||||
if output_dir is not None:
|
|
||||||
output_filename = os.path.join(output_dir, output_filename)
|
|
||||||
|
|
||||||
if self._need_link(objects, output_filename):
|
|
||||||
ldflags = self._ldflags[target_desc, debug]
|
|
||||||
|
|
||||||
export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]
|
|
||||||
|
|
||||||
ld_args = (ldflags + lib_opts + export_opts +
|
|
||||||
objects + ['/OUT:' + output_filename])
|
|
||||||
|
|
||||||
# The MSVC linker generates .lib and .exp files, which cannot be
|
|
||||||
# suppressed by any linker switches. The .lib files may even be
|
|
||||||
# needed! Make sure they are generated in the temporary build
|
|
||||||
# directory. Since they have different names for debug and release
|
|
||||||
# builds, they can go into the same directory.
|
|
||||||
build_temp = os.path.dirname(objects[0])
|
|
||||||
if export_symbols is not None:
|
|
||||||
(dll_name, dll_ext) = os.path.splitext(
|
|
||||||
os.path.basename(output_filename))
|
|
||||||
implib_file = os.path.join(
|
|
||||||
build_temp,
|
|
||||||
self.library_filename(dll_name))
|
|
||||||
ld_args.append ('/IMPLIB:' + implib_file)
|
|
||||||
|
|
||||||
if extra_preargs:
|
|
||||||
ld_args[:0] = extra_preargs
|
|
||||||
if extra_postargs:
|
|
||||||
ld_args.extend(extra_postargs)
|
|
||||||
|
|
||||||
output_dir = os.path.dirname(os.path.abspath(output_filename))
|
|
||||||
self.mkpath(output_dir)
|
|
||||||
try:
|
|
||||||
log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))
|
|
||||||
self.spawn([self.linker] + ld_args)
|
|
||||||
self._copy_vcruntime(output_dir)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise LinkError(msg)
|
|
||||||
else:
|
|
||||||
log.debug("skipping %s (up-to-date)", output_filename)
|
|
||||||
|
|
||||||
def _copy_vcruntime(self, output_dir):
|
|
||||||
vcruntime = self._vcruntime_redist
|
|
||||||
if not vcruntime or not os.path.isfile(vcruntime):
|
|
||||||
return
|
|
||||||
|
|
||||||
if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS:
|
|
||||||
return
|
|
||||||
|
|
||||||
log.debug('Copying "%s"', vcruntime)
|
|
||||||
vcruntime = shutil.copy(vcruntime, output_dir)
|
|
||||||
os.chmod(vcruntime, stat.S_IWRITE)
|
|
||||||
|
|
||||||
def spawn(self, cmd):
|
|
||||||
old_path = os.getenv('path')
|
|
||||||
try:
|
|
||||||
os.environ['path'] = self._paths
|
|
||||||
return super().spawn(cmd)
|
|
||||||
finally:
|
|
||||||
os.environ['path'] = old_path
|
|
||||||
|
|
||||||
# -- Miscellaneous methods -----------------------------------------
|
|
||||||
# These are all used by the 'gen_lib_options() function, in
|
|
||||||
# ccompiler.py.
|
|
||||||
|
|
||||||
def library_dir_option(self, dir):
|
|
||||||
return "/LIBPATH:" + dir
|
|
||||||
|
|
||||||
def runtime_library_dir_option(self, dir):
|
|
||||||
raise DistutilsPlatformError(
|
|
||||||
"don't know how to set runtime library search path for MSVC")
|
|
||||||
|
|
||||||
def library_option(self, lib):
|
|
||||||
return self.library_filename(lib)
|
|
||||||
|
|
||||||
def find_library_file(self, dirs, lib, debug=0):
|
|
||||||
# Prefer a debugging library if found (and requested), but deal
|
|
||||||
# with it if we don't have one.
|
|
||||||
if debug:
|
|
||||||
try_names = [lib + "_d", lib]
|
|
||||||
else:
|
|
||||||
try_names = [lib]
|
|
||||||
for dir in dirs:
|
|
||||||
for name in try_names:
|
|
||||||
libfile = os.path.join(dir, self.library_filename(name))
|
|
||||||
if os.path.isfile(libfile):
|
|
||||||
return libfile
|
|
||||||
else:
|
|
||||||
# Oops, didn't find it in *any* of 'dirs'
|
|
||||||
return None
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
"""distutils.archive_util
|
|
||||||
|
|
||||||
Utility functions for creating archive files (tarballs, zip files,
|
|
||||||
that sort of thing)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from warnings import warn
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
import zipfile
|
|
||||||
except ImportError:
|
|
||||||
zipfile = None
|
|
||||||
|
|
||||||
|
|
||||||
from distutils.errors import DistutilsExecError
|
|
||||||
from distutils.spawn import spawn
|
|
||||||
from distutils.dir_util import mkpath
|
|
||||||
from distutils import log
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pwd import getpwnam
|
|
||||||
except ImportError:
|
|
||||||
getpwnam = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
from grp import getgrnam
|
|
||||||
except ImportError:
|
|
||||||
getgrnam = None
|
|
||||||
|
|
||||||
def _get_gid(name):
|
|
||||||
"""Returns a gid, given a group name."""
|
|
||||||
if getgrnam is None or name is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
result = getgrnam(name)
|
|
||||||
except KeyError:
|
|
||||||
result = None
|
|
||||||
if result is not None:
|
|
||||||
return result[2]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get_uid(name):
|
|
||||||
"""Returns an uid, given a user name."""
|
|
||||||
if getpwnam is None or name is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
result = getpwnam(name)
|
|
||||||
except KeyError:
|
|
||||||
result = None
|
|
||||||
if result is not None:
|
|
||||||
return result[2]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
|
||||||
owner=None, group=None):
|
|
||||||
"""Create a (possibly compressed) tar file from all the files under
|
|
||||||
'base_dir'.
|
|
||||||
|
|
||||||
'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or
|
|
||||||
None. ("compress" will be deprecated in Python 3.2)
|
|
||||||
|
|
||||||
'owner' and 'group' can be used to define an owner and a group for the
|
|
||||||
archive that is being built. If not provided, the current owner and group
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
The output tar file will be named 'base_dir' + ".tar", possibly plus
|
|
||||||
the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z").
|
|
||||||
|
|
||||||
Returns the output filename.
|
|
||||||
"""
|
|
||||||
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '',
|
|
||||||
'compress': ''}
|
|
||||||
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz',
|
|
||||||
'compress': '.Z'}
|
|
||||||
|
|
||||||
# flags for compression program, each element of list will be an argument
|
|
||||||
if compress is not None and compress not in compress_ext.keys():
|
|
||||||
raise ValueError(
|
|
||||||
"bad value for 'compress': must be None, 'gzip', 'bzip2', "
|
|
||||||
"'xz' or 'compress'")
|
|
||||||
|
|
||||||
archive_name = base_name + '.tar'
|
|
||||||
if compress != 'compress':
|
|
||||||
archive_name += compress_ext.get(compress, '')
|
|
||||||
|
|
||||||
mkpath(os.path.dirname(archive_name), dry_run=dry_run)
|
|
||||||
|
|
||||||
# creating the tarball
|
|
||||||
import tarfile # late import so Python build itself doesn't break
|
|
||||||
|
|
||||||
log.info('Creating tar archive')
|
|
||||||
|
|
||||||
uid = _get_uid(owner)
|
|
||||||
gid = _get_gid(group)
|
|
||||||
|
|
||||||
def _set_uid_gid(tarinfo):
|
|
||||||
if gid is not None:
|
|
||||||
tarinfo.gid = gid
|
|
||||||
tarinfo.gname = group
|
|
||||||
if uid is not None:
|
|
||||||
tarinfo.uid = uid
|
|
||||||
tarinfo.uname = owner
|
|
||||||
return tarinfo
|
|
||||||
|
|
||||||
if not dry_run:
|
|
||||||
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
|
|
||||||
try:
|
|
||||||
tar.add(base_dir, filter=_set_uid_gid)
|
|
||||||
finally:
|
|
||||||
tar.close()
|
|
||||||
|
|
||||||
# compression using `compress`
|
|
||||||
if compress == 'compress':
|
|
||||||
warn("'compress' will be deprecated.", PendingDeprecationWarning)
|
|
||||||
# the option varies depending on the platform
|
|
||||||
compressed_name = archive_name + compress_ext[compress]
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
cmd = [compress, archive_name, compressed_name]
|
|
||||||
else:
|
|
||||||
cmd = [compress, '-f', archive_name]
|
|
||||||
spawn(cmd, dry_run=dry_run)
|
|
||||||
return compressed_name
|
|
||||||
|
|
||||||
return archive_name
|
|
||||||
|
|
||||||
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
|
|
||||||
"""Create a zip file from all the files under 'base_dir'.
|
|
||||||
|
|
||||||
The output zip file will be named 'base_name' + ".zip". Uses either the
|
|
||||||
"zipfile" Python module (if available) or the InfoZIP "zip" utility
|
|
||||||
(if installed and found on the default search path). If neither tool is
|
|
||||||
available, raises DistutilsExecError. Returns the name of the output zip
|
|
||||||
file.
|
|
||||||
"""
|
|
||||||
zip_filename = base_name + ".zip"
|
|
||||||
mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
|
|
||||||
|
|
||||||
# If zipfile module is not available, try spawning an external
|
|
||||||
# 'zip' command.
|
|
||||||
if zipfile is None:
|
|
||||||
if verbose:
|
|
||||||
zipoptions = "-r"
|
|
||||||
else:
|
|
||||||
zipoptions = "-rq"
|
|
||||||
|
|
||||||
try:
|
|
||||||
spawn(["zip", zipoptions, zip_filename, base_dir],
|
|
||||||
dry_run=dry_run)
|
|
||||||
except DistutilsExecError:
|
|
||||||
# XXX really should distinguish between "couldn't find
|
|
||||||
# external 'zip' command" and "zip failed".
|
|
||||||
raise DistutilsExecError(("unable to create zip file '%s': "
|
|
||||||
"could neither import the 'zipfile' module nor "
|
|
||||||
"find a standalone zip utility") % zip_filename)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.info("creating '%s' and adding '%s' to it",
|
|
||||||
zip_filename, base_dir)
|
|
||||||
|
|
||||||
if not dry_run:
|
|
||||||
try:
|
|
||||||
zip = zipfile.ZipFile(zip_filename, "w",
|
|
||||||
compression=zipfile.ZIP_DEFLATED)
|
|
||||||
except RuntimeError:
|
|
||||||
zip = zipfile.ZipFile(zip_filename, "w",
|
|
||||||
compression=zipfile.ZIP_STORED)
|
|
||||||
|
|
||||||
if base_dir != os.curdir:
|
|
||||||
path = os.path.normpath(os.path.join(base_dir, ''))
|
|
||||||
zip.write(path, path)
|
|
||||||
log.info("adding '%s'", path)
|
|
||||||
for dirpath, dirnames, filenames in os.walk(base_dir):
|
|
||||||
for name in dirnames:
|
|
||||||
path = os.path.normpath(os.path.join(dirpath, name, ''))
|
|
||||||
zip.write(path, path)
|
|
||||||
log.info("adding '%s'", path)
|
|
||||||
for name in filenames:
|
|
||||||
path = os.path.normpath(os.path.join(dirpath, name))
|
|
||||||
if os.path.isfile(path):
|
|
||||||
zip.write(path, path)
|
|
||||||
log.info("adding '%s'", path)
|
|
||||||
zip.close()
|
|
||||||
|
|
||||||
return zip_filename
|
|
||||||
|
|
||||||
ARCHIVE_FORMATS = {
|
|
||||||
'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
|
|
||||||
'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
|
|
||||||
'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
|
|
||||||
'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
|
|
||||||
'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
|
|
||||||
'zip': (make_zipfile, [],"ZIP file")
|
|
||||||
}
|
|
||||||
|
|
||||||
def check_archive_formats(formats):
|
|
||||||
"""Returns the first format from the 'format' list that is unknown.
|
|
||||||
|
|
||||||
If all formats are known, returns None
|
|
||||||
"""
|
|
||||||
for format in formats:
|
|
||||||
if format not in ARCHIVE_FORMATS:
|
|
||||||
return format
|
|
||||||
return None
|
|
||||||
|
|
||||||
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
|
|
||||||
dry_run=0, owner=None, group=None):
|
|
||||||
"""Create an archive file (eg. zip or tar).
|
|
||||||
|
|
||||||
'base_name' is the name of the file to create, minus any format-specific
|
|
||||||
extension; 'format' is the archive format: one of "zip", "tar", "gztar",
|
|
||||||
"bztar", "xztar", or "ztar".
|
|
||||||
|
|
||||||
'root_dir' is a directory that will be the root directory of the
|
|
||||||
archive; ie. we typically chdir into 'root_dir' before creating the
|
|
||||||
archive. 'base_dir' is the directory where we start archiving from;
|
|
||||||
ie. 'base_dir' will be the common prefix of all files and
|
|
||||||
directories in the archive. 'root_dir' and 'base_dir' both default
|
|
||||||
to the current directory. Returns the name of the archive file.
|
|
||||||
|
|
||||||
'owner' and 'group' are used when creating a tar archive. By default,
|
|
||||||
uses the current owner and group.
|
|
||||||
"""
|
|
||||||
save_cwd = os.getcwd()
|
|
||||||
if root_dir is not None:
|
|
||||||
log.debug("changing into '%s'", root_dir)
|
|
||||||
base_name = os.path.abspath(base_name)
|
|
||||||
if not dry_run:
|
|
||||||
os.chdir(root_dir)
|
|
||||||
|
|
||||||
if base_dir is None:
|
|
||||||
base_dir = os.curdir
|
|
||||||
|
|
||||||
kwargs = {'dry_run': dry_run}
|
|
||||||
|
|
||||||
try:
|
|
||||||
format_info = ARCHIVE_FORMATS[format]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError("unknown archive format '%s'" % format)
|
|
||||||
|
|
||||||
func = format_info[0]
|
|
||||||
for arg, val in format_info[1]:
|
|
||||||
kwargs[arg] = val
|
|
||||||
|
|
||||||
if format != 'zip':
|
|
||||||
kwargs['owner'] = owner
|
|
||||||
kwargs['group'] = group
|
|
||||||
|
|
||||||
try:
|
|
||||||
filename = func(base_name, base_dir, **kwargs)
|
|
||||||
finally:
|
|
||||||
if root_dir is not None:
|
|
||||||
log.debug("changing back to '%s'", save_cwd)
|
|
||||||
os.chdir(save_cwd)
|
|
||||||
|
|
||||||
return filename
|
|
||||||
@@ -1,393 +0,0 @@
|
|||||||
"""distutils.bcppcompiler
|
|
||||||
|
|
||||||
Contains BorlandCCompiler, an implementation of the abstract CCompiler class
|
|
||||||
for the Borland C++ compiler.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# This implementation by Lyle Johnson, based on the original msvccompiler.py
|
|
||||||
# module and using the directions originally published by Gordon Williams.
|
|
||||||
|
|
||||||
# XXX looks like there's a LOT of overlap between these two classes:
|
|
||||||
# someone should sit down and factor out the common code as
|
|
||||||
# WindowsCCompiler! --GPW
|
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
from distutils.errors import \
|
|
||||||
DistutilsExecError, DistutilsPlatformError, \
|
|
||||||
CompileError, LibError, LinkError, UnknownFileError
|
|
||||||
from distutils.ccompiler import \
|
|
||||||
CCompiler, gen_preprocess_options, gen_lib_options
|
|
||||||
from distutils.file_util import write_file
|
|
||||||
from distutils.dep_util import newer
|
|
||||||
from distutils import log
|
|
||||||
|
|
||||||
class BCPPCompiler(CCompiler) :
|
|
||||||
"""Concrete class that implements an interface to the Borland C/C++
|
|
||||||
compiler, as defined by the CCompiler abstract class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
compiler_type = 'bcpp'
|
|
||||||
|
|
||||||
# Just set this so CCompiler's constructor doesn't barf. We currently
|
|
||||||
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
|
|
||||||
# as it really isn't necessary for this sort of single-compiler class.
|
|
||||||
# Would be nice to have a consistent interface with UnixCCompiler,
|
|
||||||
# though, so it's worth thinking about.
|
|
||||||
executables = {}
|
|
||||||
|
|
||||||
# Private class data (need to distinguish C from C++ source for compiler)
|
|
||||||
_c_extensions = ['.c']
|
|
||||||
_cpp_extensions = ['.cc', '.cpp', '.cxx']
|
|
||||||
|
|
||||||
# Needed for the filename generation methods provided by the
|
|
||||||
# base class, CCompiler.
|
|
||||||
src_extensions = _c_extensions + _cpp_extensions
|
|
||||||
obj_extension = '.obj'
|
|
||||||
static_lib_extension = '.lib'
|
|
||||||
shared_lib_extension = '.dll'
|
|
||||||
static_lib_format = shared_lib_format = '%s%s'
|
|
||||||
exe_extension = '.exe'
|
|
||||||
|
|
||||||
|
|
||||||
def __init__ (self,
|
|
||||||
verbose=0,
|
|
||||||
dry_run=0,
|
|
||||||
force=0):
|
|
||||||
|
|
||||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
|
||||||
|
|
||||||
# These executables are assumed to all be in the path.
|
|
||||||
# Borland doesn't seem to use any special registry settings to
|
|
||||||
# indicate their installation locations.
|
|
||||||
|
|
||||||
self.cc = "bcc32.exe"
|
|
||||||
self.linker = "ilink32.exe"
|
|
||||||
self.lib = "tlib.exe"
|
|
||||||
|
|
||||||
self.preprocess_options = None
|
|
||||||
self.compile_options = ['/tWM', '/O2', '/q', '/g0']
|
|
||||||
self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']
|
|
||||||
|
|
||||||
self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']
|
|
||||||
self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
|
|
||||||
self.ldflags_static = []
|
|
||||||
self.ldflags_exe = ['/Gn', '/q', '/x']
|
|
||||||
self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r']
|
|
||||||
|
|
||||||
|
|
||||||
# -- Worker methods ------------------------------------------------
|
|
||||||
|
|
||||||
def compile(self, sources,
|
|
||||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
|
||||||
extra_preargs=None, extra_postargs=None, depends=None):
|
|
||||||
|
|
||||||
macros, objects, extra_postargs, pp_opts, build = \
|
|
||||||
self._setup_compile(output_dir, macros, include_dirs, sources,
|
|
||||||
depends, extra_postargs)
|
|
||||||
compile_opts = extra_preargs or []
|
|
||||||
compile_opts.append ('-c')
|
|
||||||
if debug:
|
|
||||||
compile_opts.extend (self.compile_options_debug)
|
|
||||||
else:
|
|
||||||
compile_opts.extend (self.compile_options)
|
|
||||||
|
|
||||||
for obj in objects:
|
|
||||||
try:
|
|
||||||
src, ext = build[obj]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
# XXX why do the normpath here?
|
|
||||||
src = os.path.normpath(src)
|
|
||||||
obj = os.path.normpath(obj)
|
|
||||||
# XXX _setup_compile() did a mkpath() too but before the normpath.
|
|
||||||
# Is it possible to skip the normpath?
|
|
||||||
self.mkpath(os.path.dirname(obj))
|
|
||||||
|
|
||||||
if ext == '.res':
|
|
||||||
# This is already a binary file -- skip it.
|
|
||||||
continue # the 'for' loop
|
|
||||||
if ext == '.rc':
|
|
||||||
# This needs to be compiled to a .res file -- do it now.
|
|
||||||
try:
|
|
||||||
self.spawn (["brcc32", "-fo", obj, src])
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise CompileError(msg)
|
|
||||||
continue # the 'for' loop
|
|
||||||
|
|
||||||
# The next two are both for the real compiler.
|
|
||||||
if ext in self._c_extensions:
|
|
||||||
input_opt = ""
|
|
||||||
elif ext in self._cpp_extensions:
|
|
||||||
input_opt = "-P"
|
|
||||||
else:
|
|
||||||
# Unknown file type -- no extra options. The compiler
|
|
||||||
# will probably fail, but let it just in case this is a
|
|
||||||
# file the compiler recognizes even if we don't.
|
|
||||||
input_opt = ""
|
|
||||||
|
|
||||||
output_opt = "-o" + obj
|
|
||||||
|
|
||||||
# Compiler command line syntax is: "bcc32 [options] file(s)".
|
|
||||||
# Note that the source file names must appear at the end of
|
|
||||||
# the command line.
|
|
||||||
try:
|
|
||||||
self.spawn ([self.cc] + compile_opts + pp_opts +
|
|
||||||
[input_opt, output_opt] +
|
|
||||||
extra_postargs + [src])
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise CompileError(msg)
|
|
||||||
|
|
||||||
return objects
|
|
||||||
|
|
||||||
# compile ()
|
|
||||||
|
|
||||||
|
|
||||||
def create_static_lib (self,
|
|
||||||
objects,
|
|
||||||
output_libname,
|
|
||||||
output_dir=None,
|
|
||||||
debug=0,
|
|
||||||
target_lang=None):
|
|
||||||
|
|
||||||
(objects, output_dir) = self._fix_object_args (objects, output_dir)
|
|
||||||
output_filename = \
|
|
||||||
self.library_filename (output_libname, output_dir=output_dir)
|
|
||||||
|
|
||||||
if self._need_link (objects, output_filename):
|
|
||||||
lib_args = [output_filename, '/u'] + objects
|
|
||||||
if debug:
|
|
||||||
pass # XXX what goes here?
|
|
||||||
try:
|
|
||||||
self.spawn ([self.lib] + lib_args)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise LibError(msg)
|
|
||||||
else:
|
|
||||||
log.debug("skipping %s (up-to-date)", output_filename)
|
|
||||||
|
|
||||||
# create_static_lib ()
|
|
||||||
|
|
||||||
|
|
||||||
def link (self,
|
|
||||||
target_desc,
|
|
||||||
objects,
|
|
||||||
output_filename,
|
|
||||||
output_dir=None,
|
|
||||||
libraries=None,
|
|
||||||
library_dirs=None,
|
|
||||||
runtime_library_dirs=None,
|
|
||||||
export_symbols=None,
|
|
||||||
debug=0,
|
|
||||||
extra_preargs=None,
|
|
||||||
extra_postargs=None,
|
|
||||||
build_temp=None,
|
|
||||||
target_lang=None):
|
|
||||||
|
|
||||||
# XXX this ignores 'build_temp'! should follow the lead of
|
|
||||||
# msvccompiler.py
|
|
||||||
|
|
||||||
(objects, output_dir) = self._fix_object_args (objects, output_dir)
|
|
||||||
(libraries, library_dirs, runtime_library_dirs) = \
|
|
||||||
self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)
|
|
||||||
|
|
||||||
if runtime_library_dirs:
|
|
||||||
log.warn("I don't know what to do with 'runtime_library_dirs': %s",
|
|
||||||
str(runtime_library_dirs))
|
|
||||||
|
|
||||||
if output_dir is not None:
|
|
||||||
output_filename = os.path.join (output_dir, output_filename)
|
|
||||||
|
|
||||||
if self._need_link (objects, output_filename):
|
|
||||||
|
|
||||||
# Figure out linker args based on type of target.
|
|
||||||
if target_desc == CCompiler.EXECUTABLE:
|
|
||||||
startup_obj = 'c0w32'
|
|
||||||
if debug:
|
|
||||||
ld_args = self.ldflags_exe_debug[:]
|
|
||||||
else:
|
|
||||||
ld_args = self.ldflags_exe[:]
|
|
||||||
else:
|
|
||||||
startup_obj = 'c0d32'
|
|
||||||
if debug:
|
|
||||||
ld_args = self.ldflags_shared_debug[:]
|
|
||||||
else:
|
|
||||||
ld_args = self.ldflags_shared[:]
|
|
||||||
|
|
||||||
|
|
||||||
# Create a temporary exports file for use by the linker
|
|
||||||
if export_symbols is None:
|
|
||||||
def_file = ''
|
|
||||||
else:
|
|
||||||
head, tail = os.path.split (output_filename)
|
|
||||||
modname, ext = os.path.splitext (tail)
|
|
||||||
temp_dir = os.path.dirname(objects[0]) # preserve tree structure
|
|
||||||
def_file = os.path.join (temp_dir, '%s.def' % modname)
|
|
||||||
contents = ['EXPORTS']
|
|
||||||
for sym in (export_symbols or []):
|
|
||||||
contents.append(' %s=_%s' % (sym, sym))
|
|
||||||
self.execute(write_file, (def_file, contents),
|
|
||||||
"writing %s" % def_file)
|
|
||||||
|
|
||||||
# Borland C++ has problems with '/' in paths
|
|
||||||
objects2 = map(os.path.normpath, objects)
|
|
||||||
# split objects in .obj and .res files
|
|
||||||
# Borland C++ needs them at different positions in the command line
|
|
||||||
objects = [startup_obj]
|
|
||||||
resources = []
|
|
||||||
for file in objects2:
|
|
||||||
(base, ext) = os.path.splitext(os.path.normcase(file))
|
|
||||||
if ext == '.res':
|
|
||||||
resources.append(file)
|
|
||||||
else:
|
|
||||||
objects.append(file)
|
|
||||||
|
|
||||||
|
|
||||||
for l in library_dirs:
|
|
||||||
ld_args.append("/L%s" % os.path.normpath(l))
|
|
||||||
ld_args.append("/L.") # we sometimes use relative paths
|
|
||||||
|
|
||||||
# list of object files
|
|
||||||
ld_args.extend(objects)
|
|
||||||
|
|
||||||
# XXX the command-line syntax for Borland C++ is a bit wonky;
|
|
||||||
# certain filenames are jammed together in one big string, but
|
|
||||||
# comma-delimited. This doesn't mesh too well with the
|
|
||||||
# Unix-centric attitude (with a DOS/Windows quoting hack) of
|
|
||||||
# 'spawn()', so constructing the argument list is a bit
|
|
||||||
# awkward. Note that doing the obvious thing and jamming all
|
|
||||||
# the filenames and commas into one argument would be wrong,
|
|
||||||
# because 'spawn()' would quote any filenames with spaces in
|
|
||||||
# them. Arghghh!. Apparently it works fine as coded...
|
|
||||||
|
|
||||||
# name of dll/exe file
|
|
||||||
ld_args.extend([',',output_filename])
|
|
||||||
# no map file and start libraries
|
|
||||||
ld_args.append(',,')
|
|
||||||
|
|
||||||
for lib in libraries:
|
|
||||||
# see if we find it and if there is a bcpp specific lib
|
|
||||||
# (xxx_bcpp.lib)
|
|
||||||
libfile = self.find_library_file(library_dirs, lib, debug)
|
|
||||||
if libfile is None:
|
|
||||||
ld_args.append(lib)
|
|
||||||
# probably a BCPP internal library -- don't warn
|
|
||||||
else:
|
|
||||||
# full name which prefers bcpp_xxx.lib over xxx.lib
|
|
||||||
ld_args.append(libfile)
|
|
||||||
|
|
||||||
# some default libraries
|
|
||||||
ld_args.append ('import32')
|
|
||||||
ld_args.append ('cw32mt')
|
|
||||||
|
|
||||||
# def file for export symbols
|
|
||||||
ld_args.extend([',',def_file])
|
|
||||||
# add resource files
|
|
||||||
ld_args.append(',')
|
|
||||||
ld_args.extend(resources)
|
|
||||||
|
|
||||||
|
|
||||||
if extra_preargs:
|
|
||||||
ld_args[:0] = extra_preargs
|
|
||||||
if extra_postargs:
|
|
||||||
ld_args.extend(extra_postargs)
|
|
||||||
|
|
||||||
self.mkpath (os.path.dirname (output_filename))
|
|
||||||
try:
|
|
||||||
self.spawn ([self.linker] + ld_args)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
raise LinkError(msg)
|
|
||||||
|
|
||||||
else:
|
|
||||||
log.debug("skipping %s (up-to-date)", output_filename)
|
|
||||||
|
|
||||||
# link ()
|
|
||||||
|
|
||||||
# -- Miscellaneous methods -----------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def find_library_file (self, dirs, lib, debug=0):
|
|
||||||
# List of effective library names to try, in order of preference:
|
|
||||||
# xxx_bcpp.lib is better than xxx.lib
|
|
||||||
# and xxx_d.lib is better than xxx.lib if debug is set
|
|
||||||
#
|
|
||||||
# The "_bcpp" suffix is to handle a Python installation for people
|
|
||||||
# with multiple compilers (primarily Distutils hackers, I suspect
|
|
||||||
# ;-). The idea is they'd have one static library for each
|
|
||||||
# compiler they care about, since (almost?) every Windows compiler
|
|
||||||
# seems to have a different format for static libraries.
|
|
||||||
if debug:
|
|
||||||
dlib = (lib + "_d")
|
|
||||||
try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)
|
|
||||||
else:
|
|
||||||
try_names = (lib + "_bcpp", lib)
|
|
||||||
|
|
||||||
for dir in dirs:
|
|
||||||
for name in try_names:
|
|
||||||
libfile = os.path.join(dir, self.library_filename(name))
|
|
||||||
if os.path.exists(libfile):
|
|
||||||
return libfile
|
|
||||||
else:
|
|
||||||
# Oops, didn't find it in *any* of 'dirs'
|
|
||||||
return None
|
|
||||||
|
|
||||||
# overwrite the one from CCompiler to support rc and res-files
|
|
||||||
def object_filenames (self,
|
|
||||||
source_filenames,
|
|
||||||
strip_dir=0,
|
|
||||||
output_dir=''):
|
|
||||||
if output_dir is None: output_dir = ''
|
|
||||||
obj_names = []
|
|
||||||
for src_name in source_filenames:
|
|
||||||
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
|
|
||||||
(base, ext) = os.path.splitext (os.path.normcase(src_name))
|
|
||||||
if ext not in (self.src_extensions + ['.rc','.res']):
|
|
||||||
raise UnknownFileError("unknown file type '%s' (from '%s')" % \
|
|
||||||
(ext, src_name))
|
|
||||||
if strip_dir:
|
|
||||||
base = os.path.basename (base)
|
|
||||||
if ext == '.res':
|
|
||||||
# these can go unchanged
|
|
||||||
obj_names.append (os.path.join (output_dir, base + ext))
|
|
||||||
elif ext == '.rc':
|
|
||||||
# these need to be compiled to .res-files
|
|
||||||
obj_names.append (os.path.join (output_dir, base + '.res'))
|
|
||||||
else:
|
|
||||||
obj_names.append (os.path.join (output_dir,
|
|
||||||
base + self.obj_extension))
|
|
||||||
return obj_names
|
|
||||||
|
|
||||||
# object_filenames ()
|
|
||||||
|
|
||||||
def preprocess (self,
|
|
||||||
source,
|
|
||||||
output_file=None,
|
|
||||||
macros=None,
|
|
||||||
include_dirs=None,
|
|
||||||
extra_preargs=None,
|
|
||||||
extra_postargs=None):
|
|
||||||
|
|
||||||
(_, macros, include_dirs) = \
|
|
||||||
self._fix_compile_args(None, macros, include_dirs)
|
|
||||||
pp_opts = gen_preprocess_options(macros, include_dirs)
|
|
||||||
pp_args = ['cpp32.exe'] + pp_opts
|
|
||||||
if output_file is not None:
|
|
||||||
pp_args.append('-o' + output_file)
|
|
||||||
if extra_preargs:
|
|
||||||
pp_args[:0] = extra_preargs
|
|
||||||
if extra_postargs:
|
|
||||||
pp_args.extend(extra_postargs)
|
|
||||||
pp_args.append(source)
|
|
||||||
|
|
||||||
# We need to preprocess: either we're being forced to, or the
|
|
||||||
# source file is newer than the target (or the target doesn't
|
|
||||||
# exist).
|
|
||||||
if self.force or output_file is None or newer(source, output_file):
|
|
||||||
if output_file:
|
|
||||||
self.mkpath(os.path.dirname(output_file))
|
|
||||||
try:
|
|
||||||
self.spawn(pp_args)
|
|
||||||
except DistutilsExecError as msg:
|
|
||||||
print(msg)
|
|
||||||
raise CompileError(msg)
|
|
||||||
|
|
||||||
# preprocess()
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,434 +0,0 @@
|
|||||||
"""distutils.cmd
|
|
||||||
|
|
||||||
Provides the Command class, the base class for the command classes
|
|
||||||
in the distutils.command package.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys, os, re
|
|
||||||
from distutils.errors import DistutilsOptionError
|
|
||||||
from distutils import util, dir_util, file_util, archive_util, dep_util
|
|
||||||
from distutils import log
|
|
||||||
|
|
||||||
class Command:
|
|
||||||
"""Abstract base class for defining command classes, the "worker bees"
|
|
||||||
of the Distutils. A useful analogy for command classes is to think of
|
|
||||||
them as subroutines with local variables called "options". The options
|
|
||||||
are "declared" in 'initialize_options()' and "defined" (given their
|
|
||||||
final values, aka "finalized") in 'finalize_options()', both of which
|
|
||||||
must be defined by every command class. The distinction between the
|
|
||||||
two is necessary because option values might come from the outside
|
|
||||||
world (command line, config file, ...), and any options dependent on
|
|
||||||
other options must be computed *after* these outside influences have
|
|
||||||
been processed -- hence 'finalize_options()'. The "body" of the
|
|
||||||
subroutine, where it does all its work based on the values of its
|
|
||||||
options, is the 'run()' method, which must also be implemented by every
|
|
||||||
command class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 'sub_commands' formalizes the notion of a "family" of commands,
|
|
||||||
# eg. "install" as the parent with sub-commands "install_lib",
|
|
||||||
# "install_headers", etc. The parent of a family of commands
|
|
||||||
# defines 'sub_commands' as a class attribute; it's a list of
|
|
||||||
# (command_name : string, predicate : unbound_method | string | None)
|
|
||||||
# tuples, where 'predicate' is a method of the parent command that
|
|
||||||
# determines whether the corresponding command is applicable in the
|
|
||||||
# current situation. (Eg. we "install_headers" is only applicable if
|
|
||||||
# we have any C header files to install.) If 'predicate' is None,
|
|
||||||
# that command is always applicable.
|
|
||||||
#
|
|
||||||
# 'sub_commands' is usually defined at the *end* of a class, because
|
|
||||||
# predicates can be unbound methods, so they must already have been
|
|
||||||
# defined. The canonical example is the "install" command.
|
|
||||||
sub_commands = []
|
|
||||||
|
|
||||||
|
|
||||||
# -- Creation/initialization methods -------------------------------
|
|
||||||
|
|
||||||
def __init__(self, dist):
|
|
||||||
"""Create and initialize a new Command object. Most importantly,
|
|
||||||
invokes the 'initialize_options()' method, which is the real
|
|
||||||
initializer and depends on the actual command being
|
|
||||||
instantiated.
|
|
||||||
"""
|
|
||||||
# late import because of mutual dependence between these classes
|
|
||||||
from distutils.dist import Distribution
|
|
||||||
|
|
||||||
if not isinstance(dist, Distribution):
|
|
||||||
raise TypeError("dist must be a Distribution instance")
|
|
||||||
if self.__class__ is Command:
|
|
||||||
raise RuntimeError("Command is an abstract class")
|
|
||||||
|
|
||||||
self.distribution = dist
|
|
||||||
self.initialize_options()
|
|
||||||
|
|
||||||
# Per-command versions of the global flags, so that the user can
|
|
||||||
# customize Distutils' behaviour command-by-command and let some
|
|
||||||
# commands fall back on the Distribution's behaviour. None means
|
|
||||||
# "not defined, check self.distribution's copy", while 0 or 1 mean
|
|
||||||
# false and true (duh). Note that this means figuring out the real
|
|
||||||
# value of each flag is a touch complicated -- hence "self._dry_run"
|
|
||||||
# will be handled by __getattr__, below.
|
|
||||||
# XXX This needs to be fixed.
|
|
||||||
self._dry_run = None
|
|
||||||
|
|
||||||
# verbose is largely ignored, but needs to be set for
|
|
||||||
# backwards compatibility (I think)?
|
|
||||||
self.verbose = dist.verbose
|
|
||||||
|
|
||||||
# Some commands define a 'self.force' option to ignore file
|
|
||||||
# timestamps, but methods defined *here* assume that
|
|
||||||
# 'self.force' exists for all commands. So define it here
|
|
||||||
# just to be safe.
|
|
||||||
self.force = None
|
|
||||||
|
|
||||||
# The 'help' flag is just used for command-line parsing, so
|
|
||||||
# none of that complicated bureaucracy is needed.
|
|
||||||
self.help = 0
|
|
||||||
|
|
||||||
# 'finalized' records whether or not 'finalize_options()' has been
|
|
||||||
# called. 'finalize_options()' itself should not pay attention to
|
|
||||||
# this flag: it is the business of 'ensure_finalized()', which
|
|
||||||
# always calls 'finalize_options()', to respect/update it.
|
|
||||||
self.finalized = 0
|
|
||||||
|
|
||||||
# XXX A more explicit way to customize dry_run would be better.
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr == 'dry_run':
|
|
||||||
myval = getattr(self, "_" + attr)
|
|
||||||
if myval is None:
|
|
||||||
return getattr(self.distribution, attr)
|
|
||||||
else:
|
|
||||||
return myval
|
|
||||||
else:
|
|
||||||
raise AttributeError(attr)
|
|
||||||
|
|
||||||
def ensure_finalized(self):
|
|
||||||
if not self.finalized:
|
|
||||||
self.finalize_options()
|
|
||||||
self.finalized = 1
|
|
||||||
|
|
||||||
# Subclasses must define:
|
|
||||||
# initialize_options()
|
|
||||||
# provide default values for all options; may be customized by
|
|
||||||
# setup script, by options from config file(s), or by command-line
|
|
||||||
# options
|
|
||||||
# finalize_options()
|
|
||||||
# decide on the final values for all options; this is called
|
|
||||||
# after all possible intervention from the outside world
|
|
||||||
# (command-line, option file, etc.) has been processed
|
|
||||||
# run()
|
|
||||||
# run the command: do whatever it is we're here to do,
|
|
||||||
# controlled by the command's various option values
|
|
||||||
|
|
||||||
def initialize_options(self):
|
|
||||||
"""Set default values for all the options that this command
|
|
||||||
supports. Note that these defaults may be overridden by other
|
|
||||||
commands, by the setup script, by config files, or by the
|
|
||||||
command-line. Thus, this is not the place to code dependencies
|
|
||||||
between options; generally, 'initialize_options()' implementations
|
|
||||||
are just a bunch of "self.foo = None" assignments.
|
|
||||||
|
|
||||||
This method must be implemented by all command classes.
|
|
||||||
"""
|
|
||||||
raise RuntimeError("abstract method -- subclass %s must override"
|
|
||||||
% self.__class__)
|
|
||||||
|
|
||||||
def finalize_options(self):
|
|
||||||
"""Set final values for all the options that this command supports.
|
|
||||||
This is always called as late as possible, ie. after any option
|
|
||||||
assignments from the command-line or from other commands have been
|
|
||||||
done. Thus, this is the place to code option dependencies: if
|
|
||||||
'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as
|
|
||||||
long as 'foo' still has the same value it was assigned in
|
|
||||||
'initialize_options()'.
|
|
||||||
|
|
||||||
This method must be implemented by all command classes.
|
|
||||||
"""
|
|
||||||
raise RuntimeError("abstract method -- subclass %s must override"
|
|
||||||
% self.__class__)
|
|
||||||
|
|
||||||
|
|
||||||
def dump_options(self, header=None, indent=""):
|
|
||||||
from distutils.fancy_getopt import longopt_xlate
|
|
||||||
if header is None:
|
|
||||||
header = "command options for '%s':" % self.get_command_name()
|
|
||||||
self.announce(indent + header, level=log.INFO)
|
|
||||||
indent = indent + " "
|
|
||||||
for (option, _, _) in self.user_options:
|
|
||||||
option = option.translate(longopt_xlate)
|
|
||||||
if option[-1] == "=":
|
|
||||||
option = option[:-1]
|
|
||||||
value = getattr(self, option)
|
|
||||||
self.announce(indent + "%s = %s" % (option, value),
|
|
||||||
level=log.INFO)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""A command's raison d'etre: carry out the action it exists to
|
|
||||||
perform, controlled by the options initialized in
|
|
||||||
'initialize_options()', customized by other commands, the setup
|
|
||||||
script, the command-line, and config files, and finalized in
|
|
||||||
'finalize_options()'. All terminal output and filesystem
|
|
||||||
interaction should be done by 'run()'.
|
|
||||||
|
|
||||||
This method must be implemented by all command classes.
|
|
||||||
"""
|
|
||||||
raise RuntimeError("abstract method -- subclass %s must override"
|
|
||||||
% self.__class__)
|
|
||||||
|
|
||||||
def announce(self, msg, level=1):
|
|
||||||
"""If the current verbosity level is of greater than or equal to
|
|
||||||
'level' print 'msg' to stdout.
|
|
||||||
"""
|
|
||||||
log.log(level, msg)
|
|
||||||
|
|
||||||
def debug_print(self, msg):
|
|
||||||
"""Print 'msg' to stdout if the global DEBUG (taken from the
|
|
||||||
DISTUTILS_DEBUG environment variable) flag is true.
|
|
||||||
"""
|
|
||||||
from distutils.debug import DEBUG
|
|
||||||
if DEBUG:
|
|
||||||
print(msg)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
|
|
||||||
# -- Option validation methods -------------------------------------
|
|
||||||
# (these are very handy in writing the 'finalize_options()' method)
|
|
||||||
#
|
|
||||||
# NB. the general philosophy here is to ensure that a particular option
|
|
||||||
# value meets certain type and value constraints. If not, we try to
|
|
||||||
# force it into conformance (eg. if we expect a list but have a string,
|
|
||||||
# split the string on comma and/or whitespace). If we can't force the
|
|
||||||
# option into conformance, raise DistutilsOptionError. Thus, command
|
|
||||||
# classes need do nothing more than (eg.)
|
|
||||||
# self.ensure_string_list('foo')
|
|
||||||
# and they can be guaranteed that thereafter, self.foo will be
|
|
||||||
# a list of strings.
|
|
||||||
|
|
||||||
def _ensure_stringlike(self, option, what, default=None):
|
|
||||||
val = getattr(self, option)
|
|
||||||
if val is None:
|
|
||||||
setattr(self, option, default)
|
|
||||||
return default
|
|
||||||
elif not isinstance(val, str):
|
|
||||||
raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
|
|
||||||
% (option, what, val))
|
|
||||||
return val
|
|
||||||
|
|
||||||
def ensure_string(self, option, default=None):
|
|
||||||
"""Ensure that 'option' is a string; if not defined, set it to
|
|
||||||
'default'.
|
|
||||||
"""
|
|
||||||
self._ensure_stringlike(option, "string", default)
|
|
||||||
|
|
||||||
def ensure_string_list(self, option):
|
|
||||||
r"""Ensure that 'option' is a list of strings. If 'option' is
|
|
||||||
currently a string, we split it either on /,\s*/ or /\s+/, so
|
|
||||||
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
|
|
||||||
["foo", "bar", "baz"].
|
|
||||||
"""
|
|
||||||
val = getattr(self, option)
|
|
||||||
if val is None:
|
|
||||||
return
|
|
||||||
elif isinstance(val, str):
|
|
||||||
setattr(self, option, re.split(r',\s*|\s+', val))
|
|
||||||
else:
|
|
||||||
if isinstance(val, list):
|
|
||||||
ok = all(isinstance(v, str) for v in val)
|
|
||||||
else:
|
|
||||||
ok = False
|
|
||||||
if not ok:
|
|
||||||
raise DistutilsOptionError(
|
|
||||||
"'%s' must be a list of strings (got %r)"
|
|
||||||
% (option, val))
|
|
||||||
|
|
||||||
def _ensure_tested_string(self, option, tester, what, error_fmt,
|
|
||||||
default=None):
|
|
||||||
val = self._ensure_stringlike(option, what, default)
|
|
||||||
if val is not None and not tester(val):
|
|
||||||
raise DistutilsOptionError(("error in '%s' option: " + error_fmt)
|
|
||||||
% (option, val))
|
|
||||||
|
|
||||||
def ensure_filename(self, option):
|
|
||||||
"""Ensure that 'option' is the name of an existing file."""
|
|
||||||
self._ensure_tested_string(option, os.path.isfile,
|
|
||||||
"filename",
|
|
||||||
"'%s' does not exist or is not a file")
|
|
||||||
|
|
||||||
def ensure_dirname(self, option):
|
|
||||||
self._ensure_tested_string(option, os.path.isdir,
|
|
||||||
"directory name",
|
|
||||||
"'%s' does not exist or is not a directory")
|
|
||||||
|
|
||||||
|
|
||||||
# -- Convenience methods for commands ------------------------------
|
|
||||||
|
|
||||||
def get_command_name(self):
|
|
||||||
if hasattr(self, 'command_name'):
|
|
||||||
return self.command_name
|
|
||||||
else:
|
|
||||||
return self.__class__.__name__
|
|
||||||
|
|
||||||
def set_undefined_options(self, src_cmd, *option_pairs):
|
|
||||||
"""Set the values of any "undefined" options from corresponding
|
|
||||||
option values in some other command object. "Undefined" here means
|
|
||||||
"is None", which is the convention used to indicate that an option
|
|
||||||
has not been changed between 'initialize_options()' and
|
|
||||||
'finalize_options()'. Usually called from 'finalize_options()' for
|
|
||||||
options that depend on some other command rather than another
|
|
||||||
option of the same command. 'src_cmd' is the other command from
|
|
||||||
which option values will be taken (a command object will be created
|
|
||||||
for it if necessary); the remaining arguments are
|
|
||||||
'(src_option,dst_option)' tuples which mean "take the value of
|
|
||||||
'src_option' in the 'src_cmd' command object, and copy it to
|
|
||||||
'dst_option' in the current command object".
|
|
||||||
"""
|
|
||||||
# Option_pairs: list of (src_option, dst_option) tuples
|
|
||||||
src_cmd_obj = self.distribution.get_command_obj(src_cmd)
|
|
||||||
src_cmd_obj.ensure_finalized()
|
|
||||||
for (src_option, dst_option) in option_pairs:
|
|
||||||
if getattr(self, dst_option) is None:
|
|
||||||
setattr(self, dst_option, getattr(src_cmd_obj, src_option))
|
|
||||||
|
|
||||||
def get_finalized_command(self, command, create=1):
|
|
||||||
"""Wrapper around Distribution's 'get_command_obj()' method: find
|
|
||||||
(create if necessary and 'create' is true) the command object for
|
|
||||||
'command', call its 'ensure_finalized()' method, and return the
|
|
||||||
finalized command object.
|
|
||||||
"""
|
|
||||||
cmd_obj = self.distribution.get_command_obj(command, create)
|
|
||||||
cmd_obj.ensure_finalized()
|
|
||||||
return cmd_obj
|
|
||||||
|
|
||||||
# XXX rename to 'get_reinitialized_command()'? (should do the
|
|
||||||
# same in dist.py, if so)
|
|
||||||
def reinitialize_command(self, command, reinit_subcommands=0):
|
|
||||||
return self.distribution.reinitialize_command(command,
|
|
||||||
reinit_subcommands)
|
|
||||||
|
|
||||||
def run_command(self, command):
|
|
||||||
"""Run some other command: uses the 'run_command()' method of
|
|
||||||
Distribution, which creates and finalizes the command object if
|
|
||||||
necessary and then invokes its 'run()' method.
|
|
||||||
"""
|
|
||||||
self.distribution.run_command(command)
|
|
||||||
|
|
||||||
def get_sub_commands(self):
|
|
||||||
"""Determine the sub-commands that are relevant in the current
|
|
||||||
distribution (ie., that need to be run). This is based on the
|
|
||||||
'sub_commands' class attribute: each tuple in that list may include
|
|
||||||
a method that we call to determine if the subcommand needs to be
|
|
||||||
run for the current distribution. Return a list of command names.
|
|
||||||
"""
|
|
||||||
commands = []
|
|
||||||
for (cmd_name, method) in self.sub_commands:
|
|
||||||
if method is None or method(self):
|
|
||||||
commands.append(cmd_name)
|
|
||||||
return commands
|
|
||||||
|
|
||||||
|
|
||||||
# -- External world manipulation -----------------------------------
|
|
||||||
|
|
||||||
def warn(self, msg):
|
|
||||||
log.warn("warning: %s: %s\n", self.get_command_name(), msg)
|
|
||||||
|
|
||||||
def execute(self, func, args, msg=None, level=1):
|
|
||||||
util.execute(func, args, msg, dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def mkpath(self, name, mode=0o777):
|
|
||||||
dir_util.mkpath(name, mode, dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1,
|
|
||||||
link=None, level=1):
|
|
||||||
"""Copy a file respecting verbose, dry-run and force flags. (The
|
|
||||||
former two default to whatever is in the Distribution object, and
|
|
||||||
the latter defaults to false for commands that don't define it.)"""
|
|
||||||
return file_util.copy_file(infile, outfile, preserve_mode,
|
|
||||||
preserve_times, not self.force, link,
|
|
||||||
dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def copy_tree(self, infile, outfile, preserve_mode=1, preserve_times=1,
|
|
||||||
preserve_symlinks=0, level=1):
|
|
||||||
"""Copy an entire directory tree respecting verbose, dry-run,
|
|
||||||
and force flags.
|
|
||||||
"""
|
|
||||||
return dir_util.copy_tree(infile, outfile, preserve_mode,
|
|
||||||
preserve_times, preserve_symlinks,
|
|
||||||
not self.force, dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def move_file (self, src, dst, level=1):
|
|
||||||
"""Move a file respecting dry-run flag."""
|
|
||||||
return file_util.move_file(src, dst, dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def spawn(self, cmd, search_path=1, level=1):
|
|
||||||
"""Spawn an external command respecting dry-run flag."""
|
|
||||||
from distutils.spawn import spawn
|
|
||||||
spawn(cmd, search_path, dry_run=self.dry_run)
|
|
||||||
|
|
||||||
def make_archive(self, base_name, format, root_dir=None, base_dir=None,
|
|
||||||
owner=None, group=None):
|
|
||||||
return archive_util.make_archive(base_name, format, root_dir, base_dir,
|
|
||||||
dry_run=self.dry_run,
|
|
||||||
owner=owner, group=group)
|
|
||||||
|
|
||||||
def make_file(self, infiles, outfile, func, args,
|
|
||||||
exec_msg=None, skip_msg=None, level=1):
|
|
||||||
"""Special case of 'execute()' for operations that process one or
|
|
||||||
more input files and generate one output file. Works just like
|
|
||||||
'execute()', except the operation is skipped and a different
|
|
||||||
message printed if 'outfile' already exists and is newer than all
|
|
||||||
files listed in 'infiles'. If the command defined 'self.force',
|
|
||||||
and it is true, then the command is unconditionally run -- does no
|
|
||||||
timestamp checks.
|
|
||||||
"""
|
|
||||||
if skip_msg is None:
|
|
||||||
skip_msg = "skipping %s (inputs unchanged)" % outfile
|
|
||||||
|
|
||||||
# Allow 'infiles' to be a single string
|
|
||||||
if isinstance(infiles, str):
|
|
||||||
infiles = (infiles,)
|
|
||||||
elif not isinstance(infiles, (list, tuple)):
|
|
||||||
raise TypeError(
|
|
||||||
"'infiles' must be a string, or a list or tuple of strings")
|
|
||||||
|
|
||||||
if exec_msg is None:
|
|
||||||
exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles))
|
|
||||||
|
|
||||||
# If 'outfile' must be regenerated (either because it doesn't
|
|
||||||
# exist, is out-of-date, or the 'force' flag is true) then
|
|
||||||
# perform the action that presumably regenerates it
|
|
||||||
if self.force or dep_util.newer_group(infiles, outfile):
|
|
||||||
self.execute(func, args, exec_msg, level)
|
|
||||||
# Otherwise, print the "skip" message
|
|
||||||
else:
|
|
||||||
log.debug(skip_msg)
|
|
||||||
|
|
||||||
# XXX 'install_misc' class not currently used -- it was the base class for
|
|
||||||
# both 'install_scripts' and 'install_data', but they outgrew it. It might
|
|
||||||
# still be useful for 'install_headers', though, so I'm keeping it around
|
|
||||||
# for the time being.
|
|
||||||
|
|
||||||
class install_misc(Command):
|
|
||||||
"""Common base class for installing some files in a subdirectory.
|
|
||||||
Currently used by install_data and install_scripts.
|
|
||||||
"""
|
|
||||||
|
|
||||||
user_options = [('install-dir=', 'd', "directory to install the files to")]
|
|
||||||
|
|
||||||
def initialize_options (self):
|
|
||||||
self.install_dir = None
|
|
||||||
self.outfiles = []
|
|
||||||
|
|
||||||
def _install_dir_from(self, dirname):
|
|
||||||
self.set_undefined_options('install', (dirname, 'install_dir'))
|
|
||||||
|
|
||||||
def _copy_files(self, filelist):
|
|
||||||
self.outfiles = []
|
|
||||||
if not filelist:
|
|
||||||
return
|
|
||||||
self.mkpath(self.install_dir)
|
|
||||||
for f in filelist:
|
|
||||||
self.copy_file(f, self.install_dir)
|
|
||||||
self.outfiles.append(os.path.join(self.install_dir, f))
|
|
||||||
|
|
||||||
def get_outputs(self):
|
|
||||||
return self.outfiles
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
"""distutils.command
|
|
||||||
|
|
||||||
Package containing implementation of all the standard Distutils
|
|
||||||
commands."""
|
|
||||||
|
|
||||||
__all__ = ['build',
|
|
||||||
'build_py',
|
|
||||||
'build_ext',
|
|
||||||
'build_clib',
|
|
||||||
'build_scripts',
|
|
||||||
'clean',
|
|
||||||
'install',
|
|
||||||
'install_lib',
|
|
||||||
'install_headers',
|
|
||||||
'install_scripts',
|
|
||||||
'install_data',
|
|
||||||
'sdist',
|
|
||||||
'register',
|
|
||||||
'bdist',
|
|
||||||
'bdist_dumb',
|
|
||||||
'bdist_rpm',
|
|
||||||
'bdist_wininst',
|
|
||||||
'check',
|
|
||||||
'upload',
|
|
||||||
# These two are reserved for future use:
|
|
||||||
#'bdist_sdux',
|
|
||||||
#'bdist_pkgtool',
|
|
||||||
# Note:
|
|
||||||
# bdist_packager is not included because it only provides
|
|
||||||
# an abstract base class
|
|
||||||
]
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
"""distutils.command.bdist
|
|
||||||
|
|
||||||
Implements the Distutils 'bdist' command (create a built [binary]
|
|
||||||
distribution)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from distutils.core import Command
|
|
||||||
from distutils.errors import *
|
|
||||||
from distutils.util import get_platform
|
|
||||||
|
|
||||||
|
|
||||||
def show_formats():
|
|
||||||
"""Print list of available formats (arguments to "--format" option).
|
|
||||||
"""
|
|
||||||
from distutils.fancy_getopt import FancyGetopt
|
|
||||||
formats = []
|
|
||||||
for format in bdist.format_commands:
|
|
||||||
formats.append(("formats=" + format, None,
|
|
||||||
bdist.format_command[format][1]))
|
|
||||||
pretty_printer = FancyGetopt(formats)
|
|
||||||
pretty_printer.print_help("List of available distribution formats:")
|
|
||||||
|
|
||||||
|
|
||||||
class bdist(Command):
|
|
||||||
|
|
||||||
description = "create a built (binary) distribution"
|
|
||||||
|
|
||||||
user_options = [('bdist-base=', 'b',
|
|
||||||
"temporary directory for creating built distributions"),
|
|
||||||
('plat-name=', 'p',
|
|
||||||
"platform name to embed in generated filenames "
|
|
||||||
"(default: %s)" % get_platform()),
|
|
||||||
('formats=', None,
|
|
||||||
"formats for distribution (comma-separated list)"),
|
|
||||||
('dist-dir=', 'd',
|
|
||||||
"directory to put final built distributions in "
|
|
||||||
"[default: dist]"),
|
|
||||||
('skip-build', None,
|
|
||||||
"skip rebuilding everything (for testing/debugging)"),
|
|
||||||
('owner=', 'u',
|
|
||||||
"Owner name used when creating a tar file"
|
|
||||||
" [default: current user]"),
|
|
||||||
('group=', 'g',
|
|
||||||
"Group name used when creating a tar file"
|
|
||||||
" [default: current group]"),
|
|
||||||
]
|
|
||||||
|
|
||||||
boolean_options = ['skip-build']
|
|
||||||
|
|
||||||
help_options = [
|
|
||||||
('help-formats', None,
|
|
||||||
"lists available distribution formats", show_formats),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The following commands do not take a format option from bdist
|
|
||||||
no_format_option = ('bdist_rpm',)
|
|
||||||
|
|
||||||
# This won't do in reality: will need to distinguish RPM-ish Linux,
|
|
||||||
# Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS.
|
|
||||||
default_format = {'posix': 'gztar',
|
|
||||||
'nt': 'zip'}
|
|
||||||
|
|
||||||
# Establish the preferred order (for the --help-formats option).
|
|
||||||
format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar',
|
|
||||||
'wininst', 'zip', 'msi']
|
|
||||||
|
|
||||||
# And the real information.
|
|
||||||
format_command = {'rpm': ('bdist_rpm', "RPM distribution"),
|
|
||||||
'gztar': ('bdist_dumb', "gzip'ed tar file"),
|
|
||||||
'bztar': ('bdist_dumb', "bzip2'ed tar file"),
|
|
||||||
'xztar': ('bdist_dumb', "xz'ed tar file"),
|
|
||||||
'ztar': ('bdist_dumb', "compressed tar file"),
|
|
||||||
'tar': ('bdist_dumb', "tar file"),
|
|
||||||
'wininst': ('bdist_wininst',
|
|
||||||
"Windows executable installer"),
|
|
||||||
'zip': ('bdist_dumb', "ZIP file"),
|
|
||||||
'msi': ('bdist_msi', "Microsoft Installer")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_options(self):
|
|
||||||
self.bdist_base = None
|
|
||||||
self.plat_name = None
|
|
||||||
self.formats = None
|
|
||||||
self.dist_dir = None
|
|
||||||
self.skip_build = 0
|
|
||||||
self.group = None
|
|
||||||
self.owner = None
|
|
||||||
|
|
||||||
def finalize_options(self):
|
|
||||||
# have to finalize 'plat_name' before 'bdist_base'
|
|
||||||
if self.plat_name is None:
|
|
||||||
if self.skip_build:
|
|
||||||
self.plat_name = get_platform()
|
|
||||||
else:
|
|
||||||
self.plat_name = self.get_finalized_command('build').plat_name
|
|
||||||
|
|
||||||
# 'bdist_base' -- parent of per-built-distribution-format
|
|
||||||
# temporary directories (eg. we'll probably have
|
|
||||||
# "build/bdist.<plat>/dumb", "build/bdist.<plat>/rpm", etc.)
|
|
||||||
if self.bdist_base is None:
|
|
||||||
build_base = self.get_finalized_command('build').build_base
|
|
||||||
self.bdist_base = os.path.join(build_base,
|
|
||||||
'bdist.' + self.plat_name)
|
|
||||||
|
|
||||||
self.ensure_string_list('formats')
|
|
||||||
if self.formats is None:
|
|
||||||
try:
|
|
||||||
self.formats = [self.default_format[os.name]]
|
|
||||||
except KeyError:
|
|
||||||
raise DistutilsPlatformError(
|
|
||||||
"don't know how to create built distributions "
|
|
||||||
"on platform %s" % os.name)
|
|
||||||
|
|
||||||
if self.dist_dir is None:
|
|
||||||
self.dist_dir = "dist"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# Figure out which sub-commands we need to run.
|
|
||||||
commands = []
|
|
||||||
for format in self.formats:
|
|
||||||
try:
|
|
||||||
commands.append(self.format_command[format][0])
|
|
||||||
except KeyError:
|
|
||||||
raise DistutilsOptionError("invalid format '%s'" % format)
|
|
||||||
|
|
||||||
# Reinitialize and run each command.
|
|
||||||
for i in range(len(self.formats)):
|
|
||||||
cmd_name = commands[i]
|
|
||||||
sub_cmd = self.reinitialize_command(cmd_name)
|
|
||||||
if cmd_name not in self.no_format_option:
|
|
||||||
sub_cmd.format = self.formats[i]
|
|
||||||
|
|
||||||
# passing the owner and group names for tar archiving
|
|
||||||
if cmd_name == 'bdist_dumb':
|
|
||||||
sub_cmd.owner = self.owner
|
|
||||||
sub_cmd.group = self.group
|
|
||||||
|
|
||||||
# If we're going to need to run this command again, tell it to
|
|
||||||
# keep its temporary files around so subsequent runs go faster.
|
|
||||||
if cmd_name in commands[i+1:]:
|
|
||||||
sub_cmd.keep_temp = 1
|
|
||||||
self.run_command(cmd_name)
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
"""distutils.command.bdist_dumb
|
|
||||||
|
|
||||||
Implements the Distutils 'bdist_dumb' command (create a "dumb" built
|
|
||||||
distribution -- i.e., just an archive to be unpacked under $prefix or
|
|
||||||
$exec_prefix)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from distutils.core import Command
|
|
||||||
from distutils.util import get_platform
|
|
||||||
from distutils.dir_util import remove_tree, ensure_relative
|
|
||||||
from distutils.errors import *
|
|
||||||
from distutils.sysconfig import get_python_version
|
|
||||||
from distutils import log
|
|
||||||
|
|
||||||
class bdist_dumb(Command):
|
|
||||||
|
|
||||||
description = "create a \"dumb\" built distribution"
|
|
||||||
|
|
||||||
user_options = [('bdist-dir=', 'd',
|
|
||||||
"temporary directory for creating the distribution"),
|
|
||||||
('plat-name=', 'p',
|
|
||||||
"platform name to embed in generated filenames "
|
|
||||||
"(default: %s)" % get_platform()),
|
|
||||||
('format=', 'f',
|
|
||||||
"archive format to create (tar, gztar, bztar, xztar, "
|
|
||||||
"ztar, zip)"),
|
|
||||||
('keep-temp', 'k',
|
|
||||||
"keep the pseudo-installation tree around after " +
|
|
||||||
"creating the distribution archive"),
|
|
||||||
('dist-dir=', 'd',
|
|
||||||
"directory to put final built distributions in"),
|
|
||||||
('skip-build', None,
|
|
||||||
"skip rebuilding everything (for testing/debugging)"),
|
|
||||||
('relative', None,
|
|
||||||
"build the archive using relative paths "
|
|
||||||
"(default: false)"),
|
|
||||||
('owner=', 'u',
|
|
||||||
"Owner name used when creating a tar file"
|
|
||||||
" [default: current user]"),
|
|
||||||
('group=', 'g',
|
|
||||||
"Group name used when creating a tar file"
|
|
||||||
" [default: current group]"),
|
|
||||||
]
|
|
||||||
|
|
||||||
boolean_options = ['keep-temp', 'skip-build', 'relative']
|
|
||||||
|
|
||||||
default_format = { 'posix': 'gztar',
|
|
||||||
'nt': 'zip' }
|
|
||||||
|
|
||||||
def initialize_options(self):
|
|
||||||
self.bdist_dir = None
|
|
||||||
self.plat_name = None
|
|
||||||
self.format = None
|
|
||||||
self.keep_temp = 0
|
|
||||||
self.dist_dir = None
|
|
||||||
self.skip_build = None
|
|
||||||
self.relative = 0
|
|
||||||
self.owner = None
|
|
||||||
self.group = None
|
|
||||||
|
|
||||||
def finalize_options(self):
|
|
||||||
if self.bdist_dir is None:
|
|
||||||
bdist_base = self.get_finalized_command('bdist').bdist_base
|
|
||||||
self.bdist_dir = os.path.join(bdist_base, 'dumb')
|
|
||||||
|
|
||||||
if self.format is None:
|
|
||||||
try:
|
|
||||||
self.format = self.default_format[os.name]
|
|
||||||
except KeyError:
|
|
||||||
raise DistutilsPlatformError(
|
|
||||||
"don't know how to create dumb built distributions "
|
|
||||||
"on platform %s" % os.name)
|
|
||||||
|
|
||||||
self.set_undefined_options('bdist',
|
|
||||||
('dist_dir', 'dist_dir'),
|
|
||||||
('plat_name', 'plat_name'),
|
|
||||||
('skip_build', 'skip_build'))
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.skip_build:
|
|
||||||
self.run_command('build')
|
|
||||||
|
|
||||||
install = self.reinitialize_command('install', reinit_subcommands=1)
|
|
||||||
install.root = self.bdist_dir
|
|
||||||
install.skip_build = self.skip_build
|
|
||||||
install.warn_dir = 0
|
|
||||||
|
|
||||||
log.info("installing to %s", self.bdist_dir)
|
|
||||||
self.run_command('install')
|
|
||||||
|
|
||||||
# And make an archive relative to the root of the
|
|
||||||
# pseudo-installation tree.
|
|
||||||
archive_basename = "%s.%s" % (self.distribution.get_fullname(),
|
|
||||||
self.plat_name)
|
|
||||||
|
|
||||||
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
|
|
||||||
if not self.relative:
|
|
||||||
archive_root = self.bdist_dir
|
|
||||||
else:
|
|
||||||
if (self.distribution.has_ext_modules() and
|
|
||||||
(install.install_base != install.install_platbase)):
|
|
||||||
raise DistutilsPlatformError(
|
|
||||||
"can't make a dumb built distribution where "
|
|
||||||
"base and platbase are different (%s, %s)"
|
|
||||||
% (repr(install.install_base),
|
|
||||||
repr(install.install_platbase)))
|
|
||||||
else:
|
|
||||||
archive_root = os.path.join(self.bdist_dir,
|
|
||||||
ensure_relative(install.install_base))
|
|
||||||
|
|
||||||
# Make the archive
|
|
||||||
filename = self.make_archive(pseudoinstall_root,
|
|
||||||
self.format, root_dir=archive_root,
|
|
||||||
owner=self.owner, group=self.group)
|
|
||||||
if self.distribution.has_ext_modules():
|
|
||||||
pyversion = get_python_version()
|
|
||||||
else:
|
|
||||||
pyversion = 'any'
|
|
||||||
self.distribution.dist_files.append(('bdist_dumb', pyversion,
|
|
||||||
filename))
|
|
||||||
|
|
||||||
if not self.keep_temp:
|
|
||||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user