master f6d6517952d5 cached
142 files
582.5 KB
165.3k tokens
1344 symbols
1 requests
Download .txt
Showing preview only (621K chars total). Download the full file or copy to clipboard to get everything.
Repository: PacktPublishing/Mastering-Object-Oriented-Python-Second-Edition
Branch: master
Commit: f6d6517952d5
Files: 142
Total size: 582.5 KB

Directory structure:
gitextract_if6v5z7l/

├── .pylintrc
├── Chapter_1/
│   ├── ch01_ex1.py
│   ├── ch01_ex2.py
│   ├── ch01_ex3.py
│   ├── ch01_ex4.py
│   ├── ch01_ex5.py
│   └── getting_started.rst
├── Chapter_10/
│   ├── __init__.py
│   ├── ch10_bonus.py
│   ├── ch10_ex1.py
│   ├── ch10_ex2.py
│   ├── ch10_ex2a.py
│   ├── ch10_ex2b.py
│   ├── ch10_ex2c.py
│   ├── ch10_ex3.py
│   ├── ch10_ex4.py
│   ├── ch10_ex5.py
│   └── ch10_ex6.py
├── Chapter_11/
│   ├── __init__.py
│   ├── ch11_ex1.py
│   └── ch11_ex2.py
├── Chapter_12/
│   ├── __init__.py
│   ├── ch12_ex1.py
│   ├── ch12_ex2.py
│   ├── ch12_ex3.py
│   └── ch12_ex4.py
├── Chapter_13/
│   ├── __init__.py
│   ├── cards_openapi.json
│   ├── ch13_e1_ex2.py
│   ├── ch13_e1_ex3.py
│   ├── ch13_e1_ex4.py
│   ├── ch13_ex1.py
│   ├── ch13_ex2.py
│   ├── ch13_ex3.py
│   ├── ch13_ex4.py
│   ├── ch13_ex5.py
│   ├── ch13_ex6.py
│   ├── dice_openapi.json
│   ├── dominoes_openapi.json
│   └── simulation_model.py
├── Chapter_14/
│   ├── __init__.py
│   ├── ch14_ex1.py
│   ├── ch14_ex2.py
│   ├── ch14_ex3.py
│   ├── ch14_ex4.py
│   ├── ch14_ex5.py
│   ├── ch14_ex6.py
│   ├── simulation_model.py
│   └── someapp.config
├── Chapter_15/
│   ├── __init__.py
│   ├── ch15_ex1.py
│   └── ch15_ex2.py
├── Chapter_16/
│   ├── __init__.py
│   ├── ch16_ex1.py
│   ├── ch16_ex10.py
│   ├── ch16_ex2.py
│   ├── ch16_ex3.py
│   ├── ch16_ex4.py
│   ├── ch16_ex5.py
│   ├── ch16_ex6.py
│   ├── ch16_ex7.py
│   ├── ch16_ex8.py
│   └── ch16_ex9.py
├── Chapter_17/
│   ├── __init__.py
│   ├── ch17_data.csv
│   ├── ch17_ex1.py
│   ├── ch17_ex2.py
│   └── test_ch17.py
├── Chapter_18/
│   ├── __init__.py
│   ├── ch18_demo.py
│   ├── ch18_ex1.py
│   ├── ch18_ex2.py
│   ├── ch18_ex3.py
│   ├── ch18app.yaml
│   └── opt/
│       └── ch18app.yaml
├── Chapter_19/
│   ├── __init__.py
│   ├── ch19_ex1.py
│   ├── ch19_ex2.py
│   ├── some_algorithm/
│   │   ├── __init__.py
│   │   ├── abstraction.py
│   │   ├── long_version.py
│   │   └── short_version.py
│   └── tests/
│       ├── __init__.py
│       └── test_all.py
├── Chapter_2/
│   ├── __init__.py
│   ├── ch02_ex1.py
│   ├── ch02_ex2.py
│   ├── ch02_ex3.py
│   ├── ch02_ex4.py
│   └── ch02_ex5.py
├── Chapter_20/
│   ├── README.rst
│   ├── __init__.py
│   ├── combo.py
│   ├── combo.py.html
│   ├── combo.py.txt
│   ├── docs/
│   │   ├── Makefile
│   │   ├── conf.py
│   │   ├── implementation.rst
│   │   ├── index.rst
│   │   └── user_story.rst
│   ├── src/
│   │   ├── __init__.py
│   │   └── ch20_ex1.py
│   └── tests/
│       └── test_ch20.py
├── Chapter_3/
│   ├── __init__.py
│   ├── ch03_ex1.py
│   ├── ch03_ex2.py
│   ├── ch03_ex3.py
│   ├── ch03_ex4.py
│   └── ch03_ex5.py
├── Chapter_4/
│   ├── __init__.py
│   ├── ch04_ex1.py
│   ├── ch04_ex2.py
│   ├── ch04_ex3.py
│   ├── ch04_ex4.py
│   └── ch04_ex5.py
├── Chapter_5/
│   ├── __init__.py
│   ├── ch05_ex1.py
│   └── ch05_ex2.py
├── Chapter_6/
│   ├── __init__.py
│   ├── ch06_ex1.py
│   └── ch06_ex2.py
├── Chapter_7/
│   ├── __init__.py
│   ├── ch07_defaults.json
│   ├── ch07_ex1.py
│   ├── ch07_ex2.py
│   ├── ch07_ex3.py
│   └── ch07_ex4.py
├── Chapter_8/
│   ├── __init__.py
│   └── ch08_ex1.py
├── Chapter_9/
│   ├── __init__.py
│   ├── ch09_ex1.py
│   └── ch09_ex2.py
├── LICENSE
├── README.md
├── data/
│   ├── ch17_data.csv
│   └── ch17_sample.csv
├── environment.yaml
├── requirements.txt
├── show_hierarchies.py
├── stubs/
│   └── sqlite3.pyi
├── test_all.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .pylintrc
================================================
[MASTER]

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS

# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=

# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=

# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1

# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100

# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=

# Pickle collected data for later comparisons.
persistent=yes

# Specify a configuration file.
#rcfile=

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no


[MESSAGES CONTROL]

# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=invalid-name,
        missing-docstring,
        print-statement,
        parameter-unpacking,
        unpacking-in-except,
        old-raise-syntax,
        backtick,
        long-suffix,
        old-ne-operator,
        old-octal-literal,
        import-star-module-level,
        non-ascii-bytes-literal,
        raw-checker-failed,
        bad-inline-option,
        locally-disabled,
        locally-enabled,
        file-ignored,
        suppressed-message,
        useless-suppression,
        deprecated-pragma,
        use-symbolic-message-instead,
        too-few-public-methods,
        unused-import,
        apply-builtin,
        basestring-builtin,
        buffer-builtin,
        cmp-builtin,
        coerce-builtin,
        execfile-builtin,
        file-builtin,
        long-builtin,
        raw_input-builtin,
        reduce-builtin,
        standarderror-builtin,
        unicode-builtin,
        xrange-builtin,
        coerce-method,
        delslice-method,
        getslice-method,
        setslice-method,
        no-absolute-import,
        old-division,
        dict-iter-method,
        dict-view-method,
        next-method-called,
        metaclass-assignment,
        indexing-exception,
        raising-string,
        reload-builtin,
        oct-method,
        hex-method,
        nonzero-method,
        cmp-method,
        input-builtin,
        round-builtin,
        intern-builtin,
        unichr-builtin,
        map-builtin-not-iterating,
        zip-builtin-not-iterating,
        range-builtin-not-iterating,
        filter-builtin-not-iterating,
        using-cmp-argument,
        eq-without-hash,
        div-method,
        idiv-method,
        rdiv-method,
        exception-message-attribute,
        invalid-str-codec,
        sys-max-int,
        bad-python3-import,
        deprecated-string-function,
        deprecated-str-translate-call,
        deprecated-itertools-function,
        deprecated-types-field,
        next-method-defined,
        dict-items-not-iterating,
        dict-keys-not-iterating,
        dict-values-not-iterating,
        deprecated-operator-function,
        deprecated-urllib-function,
        xreadlines-attribute,
        deprecated-sys-function,
        exception-escape,
        comprehension-escape,
        wrong-import-order

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member


[REPORTS]

# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)

# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=

# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text

# Tells whether to display a full report or only the messages.
reports=no

# Activate the evaluation score.
score=yes


[REFACTORING]

# Maximum number of nested blocks for function / method body
max-nested-blocks=5

# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit


[LOGGING]

# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging


[SPELLING]

# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4

# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package..
spelling-dict=

# List of comma separated words that should not be checked.
spelling-ignore-words=

# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=

# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no


[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
      XXX,
      TODO


[TYPECHECK]

# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager

# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=

# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes

# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes

# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes

# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes

# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1

# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1


[VARIABLES]

# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=

# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes

# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
          _cb

# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_

# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_

# Tells whether we should check for unused import in __init__ files.
init-import=no

# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io


[FORMAT]

# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

# Number of spaces of indent required inside a hanging  or continued line.
indent-after-paren=4

# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
# tab).
indent-string='    '

# Maximum number of characters on a single line.
max-line-length=100

# Maximum number of lines in a module.
max-module-lines=1000

# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
               dict-separator

# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no

# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no


[SIMILARITIES]

# Ignore comments when computing similarities.
ignore-comments=yes

# Ignore docstrings when computing similarities.
ignore-docstrings=yes

# Ignore imports when computing similarities.
ignore-imports=no

# Minimum lines number of a similarity.
min-similarity-lines=4


[BASIC]

# Naming style matching correct argument names.
argument-naming-style=snake_case

# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=

# Naming style matching correct attribute names.
attr-naming-style=snake_case

# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=

# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
          bar,
          baz,
          toto,
          tutu,
          tata

# Naming style matching correct class attribute names.
class-attribute-naming-style=any

# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=

# Naming style matching correct class names.
class-naming-style=PascalCase

# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=

# Naming style matching correct constant names.
const-naming-style=UPPER_CASE

# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=

# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1

# Naming style matching correct function names.
function-naming-style=snake_case

# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=

# Good variable names which should always be accepted, separated by a comma.
good-names=i,
           j,
           k,
           ex,
           Run,
           _

# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no

# Naming style matching correct inline iteration names.
inlinevar-naming-style=any

# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=

# Naming style matching correct method names.
method-naming-style=snake_case

# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=

# Naming style matching correct module names.
module-naming-style=snake_case

# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=

# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=

# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_

# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty

# Naming style matching correct variable names.
variable-naming-style=snake_case

# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=


[IMPORTS]

# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no

# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no

# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix

# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=

# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=

# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=

# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=

# Force import order to recognize a module as part of a third party library.
known-third-party=enchant


[CLASSES]

# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
                      __new__,
                      setUp

# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
                  _fields,
                  _replace,
                  _source,
                  _make

# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls

# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls


[DESIGN]

# Maximum number of arguments for function / method.
max-args=5

# Maximum number of attributes for a class (see R0902).
max-attributes=7

# Maximum number of boolean expressions in an if statement.
max-bool-expr=5

# Maximum number of branch for function / method body.
max-branches=12

# Maximum number of locals for function / method body.
max-locals=15

# Maximum number of parents for a class (see R0901).
max-parents=7

# Maximum number of public methods for a class (see R0904).
max-public-methods=20

# Maximum number of return / yield for function / method body.
max-returns=6

# Maximum number of statements in function / method body.
max-statements=50

# Minimum number of public methods for a class (see R0903).
min-public-methods=2


[EXCEPTIONS]

# Exceptions that will emit a warning when being caught. Defaults to
# "Exception".
overgeneral-exceptions=Exception


================================================
FILE: Chapter_1/ch01_ex1.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 1. Example 1.
"""

# Show the basics of timeit.

import timeit

method_time = timeit.timeit(
    "obj.method()",
    """
class SomeClass:
    def method(self):
        pass
obj= SomeClass()
""",
)

function_time = timeit.timeit(
    "f()",
    """
def f():
    pass
""",
)

if __name__ == "__main__":
    print(f"Method   {method_time:.4f}")
    print(f"Function {function_time:.4f}")


================================================
FILE: Chapter_1/ch01_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 1. Example 2.
"""

# pylint: disable=invalid-name
test_list = """
    >>> f = [1, 1, 2, 3]
    >>> f += [f[-1] + f[-2]]
    >>> f
    [1, 1, 2, 3, 5]
    
    >>> f.__getitem__(-1)
    5
    >>> f.__getitem__(-1).__add__(f.__getitem__(-2))
    8
    >>> f.__iadd__([8])
    [1, 1, 2, 3, 5, 8]
    >>> f
    [1, 1, 2, 3, 5, 8]
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=False)


================================================
FILE: Chapter_1/ch01_ex3.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 1. Example 3.
"""

def F(n: int) -> int:
    if n in (0, 1):
        return 1
    else:
        return F(n-1) + F(n-2)

test_F_8 = """
    >>> F(8)
    34
"""

def demo():
    print("Good Use", F(8))
    print("Bad Use", F(355/113))

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=False)


================================================
FILE: Chapter_1/ch01_ex4.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 1. Example 4.
"""


# Simple function with docstring.

def factorial(n: int) -> int:
    """Compute n! recursively.

    :param n: an integer >= 0
    :returns: n!

    Because of Python's stack limitation, this won't
    compute a value larger than about 1000!.

    >>> factorial(5)
    120
    """
    if n == 0:
        return 1
    return n * factorial(n - 1)


if __name__ == "__main__":
    import doctest

    doctest.testmod()


================================================
FILE: Chapter_1/ch01_ex5.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 1. Example 5.
"""

# Definition of some classes with doctest-based unit tests.

from types import SimpleNamespace

class EmptyClass:
    pass

EmptyClass2 = SimpleNamespace

EmptyClass3 = type('EmptyClass3', (object,), {})

__test__ = {
    'EmptyClass': '''
        >>> ec = EmptyClass()
        >>> ec.new_attribute = 42
        >>> ec.new_attribute
        42
        >>> ec.undefined  # doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
        AttributeError: 'EmptyClass' object has no attribute 'undefined'
        ''',
    'EmptyClass2': '''
        >>> ec = EmptyClass2()
        >>> ec.new_attribute = 42
        >>> ec.new_attribute
        42
        >>> ec.undefined  # doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
        AttributeError: 'EmptyClass' object has no attribute 'undefined'
        ''',
    'EmptyClass3': '''
        >>> ec = EmptyClass3()
        >>> ec.new_attribute = 42
        >>> ec.new_attribute
        42
        >>> ec.undefined  # doctest: +IGNORE_EXCEPTION_DETAIL
        Traceback (most recent call last):
        AttributeError: 'EmptyClass' object has no attribute 'undefined'
        ''',
}

if __name__ == "__main__":
    import doctest
    doctest.testmod()


================================================
FILE: Chapter_1/getting_started.rst
================================================
In order to run the examples mentioned in this book you require the following software:

- Python version 3.7 or higher with the standard suite of libraries.

We'll look at some additional packages. These include PyYaml, SQLAlchemy, and Jinja2.
We'll use pytest for doing unit testing.

- http://pyyaml.org

- http://www.sqlalchemy.org When building this, check the installation guide, http://docs.sqlalchemy.org/en/rel_0_9/intro.html#installation. Using the --without-cextensions option can simplify installation.

- http://jinja.pocoo.org/

We'll also look at some tools:

- https://docs.pytest.org/en/latest/index.html

- http://sphinx-doc.org

- http://mypy-lang.org

- https://www.pylint.org

- https://github.com/ambv/black


There two alternative approaches to these installations.

-   From Python.org

    1.  Install Python 3.7 from http://www.python.org. This will change your OS-level ``PATH`` settings.
        This usually means you need to start a new terminal session to make your new Python tools available.

    2.  Use your new pip3 to install the other packages:

		  ``python3 -m pip install pyyaml sqlalchemy jinja2 pytest sphinx mypy pylint black``

    If you install from Python.org, you'll have a single, default Python environment. This isn't optimal.
    When you need to upgrade or experiment, you'll often want to create additional environments. There
    are several tools for this. The conda tool seems to be the most versatile.

-   Use Anaconda.com's miniconda to get started.

    1.  Download and install the appropriate miniconda for your platform. https://conda.io/miniconda.html

    2.  Use miniconda to build a Python environment including the required packages.

        ``conda create --name mastering python=3.7 pyyaml sqlalchemy jinja2 pytest sphinx mypy pylint``

    3.  Activate your new environment

        ``conda activate mastering``

        You'll know it's active because your terminal prompt will have ``(mastering)`` as a prefix.

    4.  Add the ``black`` tool using a separate installation after activating the environment.

        ``python3 -m pip install --upgrade pip``

        ``python3 -m pip install black``



================================================
FILE: Chapter_10/__init__.py
================================================


================================================
FILE: Chapter_10/ch10_bonus.py
================================================
"""
Enumerate all Blackjack outcomes with player mirroring the dealer choice.
Note that player going bust first is a loss, irrespective of what the dealer holds.

The question, then, is given two cards, what are the odds of going bust using
dealer rules of hit 17, stand on 18.

Then bust for player is rule 1.
Bust for dealer is rule 2.
Otherwise it's a 50/50 proposition.
"""
from typing import Optional, Tuple, Dict, Counter
import random
from enum import Enum
import collections

class Suit(Enum):
    Clubs = "♣"
    Diamonds = "♦"
    Hearts = "♥"
    Spades = "♠"

class Card:

    def __init__(self, rank: str, suit: Suit, hard: Optional[int]=None, soft: Optional[int]=None) -> None:
        self.rank = rank
        self.suit = suit
        self.hard = hard or int(rank)
        self.soft = soft or int(rank)

    def __str__(self) -> str:
        return f"{self.rank!s}{self.suit.value!s}"


class AceCard(Card):

    def __init__(self, rank: str, suit: Suit) -> None:
        super().__init__(rank, suit, 1, 11)


class FaceCard(Card):

    def __init__(self, rank: str, suit: Suit) -> None:
        super().__init__(rank, suit, 10, 10)

def card(rank: int, suit: Suit) -> Card:
    if rank == 1:
        return AceCard("A", suit)
    elif rank in (11, 12, 13):
        rank_str = {11: "J", 12: "Q", 13: "K"}[rank]
        return FaceCard(rank_str, suit)
    else:
        return Card(str(rank), suit)

class Deck(list):
    def __init__(self) -> None:
        super().__init__(card(r, s) for r in range(1, 14) for s in Suit)
        random.shuffle(self)

class Hand(list):
    @property
    def hard(self) -> int:
        return sum(c.hard for c in self)

    @property
    def soft(self) -> int:
        return sum(c.soft for c in self)

    def __repr__(self) -> str:
        cards = [str(c) for c in self]
        return f"Hand({cards!r})"

def deal_rules(deck: Deck) -> Tuple[Hand, Optional[int]]:
    hand = Hand([deck.pop(), deck.pop()])
    while hand.hard < 21:
        if hand.soft == 21:
            return hand, 21
        elif hand.hard == 21:
            return hand, 21
        elif hand.soft < 18:
            hand.append(deck.pop())
        elif hand.soft > 21 and hand.hard < 18:
            hand.append(deck.pop())
        else:
            return hand, min(hand.hard, hand.soft)
    return hand, None

def simulation() -> None:
    raw_outcomes: Counter[Tuple[Optional[int], Optional[int]]] = collections.Counter()
    game_payout: Counter[str] = collections.Counter()

    for i in range(20_000):
        deck = Deck()
        player_hand, player_result = deal_rules(deck)
        dealer_hand, dealer_result = deal_rules(deck)
        raw_outcomes[(player_result, dealer_result)] += 1
        if player_result is None:
            game_payout['loss'] += 1
        elif player_result is 21:
            game_payout['21'] += 1
        elif dealer_result is None:
            game_payout['win'] += 1
        elif player_result > dealer_result:
            game_payout['win'] += 1
        elif player_result == dealer_result:
            game_payout['push'] += 1
        else:
            game_payout['loss'] += 1

    running = 0.0
    for outcome, count in game_payout.most_common():
        print(f"{running:.3f} <= r < {running+count/20_000:.3f}: {outcome}")
        running += count/20_000

if __name__ == "__main__":
    import doctest
    doctest.testmod()

    simulation()


================================================
FILE: Chapter_10/ch10_ex1.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 1. JSON
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
from typing import List, Optional, Dict, Any, DefaultDict, Union, Type
from pathlib import Path
import datetime
from dataclasses import dataclass

# Technically, this is the type supported by JSON serailization.
# JSON = Union[Dict[str, 'JSON'], List['JSON'], int, str, float, bool, Type[None]]
JSON = Union[Dict[str, Any], List[Any], int, str, float, bool, Type[None]]

@dataclass
class Post:
    date: datetime.datetime
    title: str
    rst_text: str
    tags: List[str]

    def as_dict(self) -> Dict[str, Any]:
        return dict(
            date=str(self.date),
            title=self.title,
            underline="-" * len(self.title),
            rst_text=self.rst_text,
            tag_text=" ".join(self.tags),
        )


# Here's a collection of these posts. This is an extension
# of list which doesn't work well with JSON.

from collections import defaultdict

class Blog_x(list):

    def __init__(self, title: str, posts: Optional[List[Post]]=None) -> None:
        self.title = title
        super().__init__(posts if posts is not None else [])

    def by_tag(self) -> DefaultDict[str, List[Dict[str, Any]]]:
        tag_index: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
        for post in self:
            for tag in post.tags:
                tag_index[tag].append(post.as_dict())
        return tag_index

    def as_dict(self) -> Dict[str, Any]:
        return dict(
            title=self.title,
            entries=[p.as_dict() for p in self]
        )


# An example blog
travel_x = Blog_x("Travel")
travel_x.append(
    Post(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )
)
travel_x.append(
    Post(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including < & > characters.""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
)

# JSON
# ================================

# Example 1: Simple
# ####################

# Simple JSON dump
import json

test_json_1 = """
    >>> print(json.dumps(travel_x.as_dict(), indent=4))
    {
        "title": "Travel",
        "entries": [
            {
                "date": "2013-11-14 17:25:00",
                "title": "Hard Aground",
                "underline": "------------",
                "rst_text": "Some embarrassing revelation. Including \u2639 and \u2693",
                "tag_text": "#RedRanger #Whitby42 #ICW"
            },
            {
                "date": "2013-11-18 15:30:00",
                "title": "Anchor Follies",
                "underline": "--------------",
                "rst_text": "Some witty epigram. Including < & > characters.",
                "tag_text": "#RedRanger #Whitby42 #Mistakes"
            }
        ]
    }
    """

# Example 2. JSON: Flawed Container Design
# ########################################

# Flawed Encoder based on flawed design of the class.
def blogx_encode(object: Any) -> Dict[str, Any]:
    if isinstance(object, datetime.datetime):
        return dict(
            __class__="datetime.datetime",
            __args__=[],
            __kw__=dict(
                year=object.year,
                month=object.month,
                day=object.day,
                hour=object.hour,
                minute=object.minute,
                second=object.second,
            ),
        )
    elif isinstance(object, Post):
        return dict(
            __class__="Post",
            __args__=[],
            __kw__=dict(
                date=object.date,
                title=object.title,
                rst_text=object.rst_text,
                tags=object.tags,
            ),
        )
    elif isinstance(object, Blog_x):
        # Will get ignored...
        return dict(
            __class__="Blog_x",
            __args__=[],
            __kw__=dict(title=object.title, entries=tuple(object)),
        )
    else:
        return object

def blogx_decode(some_dict: Dict[str, Any]) -> Dict[str, Any]:
    if set(some_dict.keys()) == set(["__class__", "__args__", "__kw__"]):
        class_ = eval(some_dict["__class__"])
        return class_(*some_dict["__args__"], **some_dict["__kw__"])
    else:
        return some_dict

test_json_2 = """
    >>> text = json.dumps(travel_x, indent=4, default=blogx_encode)
    >>> print(text)
    [
        {
            "__class__": "Post",
            "__args__": [],
            "__kw__": {
                "date": {
                    "__class__": "datetime.datetime",
                    "__args__": [],
                    "__kw__": {
                        "year": 2013,
                        "month": 11,
                        "day": 14,
                        "hour": 17,
                        "minute": 25,
                        "second": 0
                    }
                },
                "title": "Hard Aground",
                "rst_text": "Some embarrassing revelation. Including \u2639 and \u2693",
                "tags": [
                    "#RedRanger",
                    "#Whitby42",
                    "#ICW"
                ]
            }
        },
        {
            "__class__": "Post",
            "__args__": [],
            "__kw__": {
                "date": {
                    "__class__": "datetime.datetime",
                    "__args__": [],
                    "__kw__": {
                        "year": 2013,
                        "month": 11,
                        "day": 18,
                        "hour": 15,
                        "minute": 30,
                        "second": 0
                    }
                },
                "title": "Anchor Follies",
                "rst_text": "Some witty epigram. Including < & > characters.",
                "tags": [
                    "#RedRanger",
                    "#Whitby42",
                    "#Mistakes"
                ]
            }
        }
    ]
        
    The Blog structure overall? Vanished. It's only a list
    >>> from pprint import pprint
    >>> copy = json.loads(text, object_hook=blogx_decode)
    >>> pprint(copy)
    [Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓', tags=['#RedRanger', '#Whitby42', '#ICW']),
     Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including < & > characters.', tags=['#RedRanger', '#Whitby42', '#Mistakes'])]

"""

# Example 3 JSON: Better Design
# ###############################

# Consider this wrap-based design instead of an extension-based version

# Here's another collection of these posts.
# This wraps a list which works much better with JSON than extending a list.

import datetime
from collections import defaultdict


class Blog:

    def __init__(self, title: str, posts: Optional[List[Post]]=None) -> None:
        self.title = title
        self.entries = posts if posts is not None else []

    @property
    def underline(self) -> str:
        return '='*len(self.title)

    def append(self, post: Post) -> None:
        self.entries.append(post)

    def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:
        tag_index: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
        for post in self.entries:
            for tag in post.tags:
                tag_index[tag].append(post.as_dict())
        return tag_index

    def as_dict(self) -> Dict[str, Any]:
        return dict(
            title=self.title,
            underline=self.underline,
            entries=[p.as_dict() for p in self.entries],
        )


# An example blog
travel = Blog("Travel")
travel.append(
    Post(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓︎""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )
)
travel.append(
    Post(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including < & > characters.""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
)


def blog_encode(object: Any) -> Dict[str, Any]:
    if isinstance(object, datetime.datetime):
        return dict(
            __class__="datetime.datetime",
            __args__=[],
            __kw__=dict(
                year=object.year,
                month=object.month,
                day=object.day,
                hour=object.hour,
                minute=object.minute,
                second=object.second,
            ),
        )
    elif isinstance(object, Post):
        return dict(
            __class__="Post",
            __args__=[],
            __kw__=dict(
                date=object.date,
                title=object.title,
                rst_text=object.rst_text,
                tags=object.tags,
            ),
        )
    elif isinstance(object, Blog):
        return dict(
            __class__="Blog", __args__=[object.title, object.entries], __kw__={}
        )
    else:
        return object


def blog_decode(some_dict: Dict[str, Any]) -> Dict[str, Any]:
    if set(some_dict.keys()) == {"__class__", "__args__", "__kw__"}:
        class_ = eval(some_dict["__class__"])
        return class_(*some_dict["__args__"], **some_dict["__kw__"])
    else:
        return some_dict


test_json_3 = """
    >>> text = json.dumps(travel, indent=4, default=blog_encode)
    >>> print(text)
    {
        "__class__": "Blog",
        "__args__": [
            "Travel",
            [
                {
                    "__class__": "Post",
                    "__args__": [],
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime",
                            "__args__": [],
                            "__kw__": {
                                "year": 2013,
                                "month": 11,
                                "day": 14,
                                "hour": 17,
                                "minute": 25,
                                "second": 0
                            }
                        },
                        "title": "Hard Aground",
                        "rst_text": "Some embarrassing revelation. Including \u2639 and \u2693\ufe0e",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#ICW"
                        ]
                    }
                },
                {
                    "__class__": "Post",
                    "__args__": [],
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime",
                            "__args__": [],
                            "__kw__": {
                                "year": 2013,
                                "month": 11,
                                "day": 18,
                                "hour": 15,
                                "minute": 30,
                                "second": 0
                            }
                        },
                        "title": "Anchor Follies",
                        "rst_text": "Some witty epigram. Including < & > characters.",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#Mistakes"
                        ]
                    }
                }
            ]
        ],
        "__kw__": {}
    }
    
    >>> from pprint import pprint
    >>> copy = json.loads(text, object_hook=blog_decode)
    >>> print(copy.title)
    Travel
    >>> pprint(copy.entries)
    [Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW']),
     Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including < & > characters.', tags=['#RedRanger', '#Whitby42', '#Mistakes'])]
"""

# Sidebar: Demo of rendering 1
# ###############################

# Here's a template for an individual post
import string

# Here's a way to render the entire blog in RST
def rst_render(blog: Blog) -> None:
    post = string.Template(
        """
    $title
    $underline

    $rst_text

    :date: $date

    :tags: $tag_text
    """
    )

    # with contextlib.redirect_stdout("some_file"):
    print(f"{blog.title}\n{blog.underline}\n")
    for p in blog.entries:
        print(post.substitute(**p.as_dict()))

    tag_index = blog.by_tag()
    print("Tag Index")
    print("=========")
    print()
    for tag in tag_index:
        print(f"*   {tag}")
        print()
        for post_dict in tag_index[tag]:
            print(f"    -   `{post_dict['title']}`_")
        print()

test_string_template_render = """
    >>> rst_render(travel)
    Travel
    ======
    <BLANKLINE>
    <BLANKLINE>
        Hard Aground
        ------------
    <BLANKLINE>
        Some embarrassing revelation. Including ☹ and ⚓︎
    <BLANKLINE>
        :date: 2013-11-14 17:25:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #ICW
    <BLANKLINE>
    <BLANKLINE>
        Anchor Follies
        --------------
    <BLANKLINE>
        Some witty epigram. Including < & > characters.
    <BLANKLINE>
        :date: 2013-11-18 15:30:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #Mistakes
    <BLANKLINE>
    Tag Index
    =========
    <BLANKLINE>
    *   #RedRanger
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #Whitby42
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #ICW
    <BLANKLINE>
        -   `Hard Aground`_
    <BLANKLINE>
    *   #Mistakes
    <BLANKLINE>
        -   `Anchor Follies`_
    <BLANKLINE>

"""

# Sidebar: Demo of rendering 2 (using Jinja2)
# ############################################

from jinja2 import Template

blog_template = Template(
"""{{title}}
{{underline}}

{% for e in entries %}
    {{e.title}}
    {{e.underline}}

    {{e.rst_text}}

    :date: {{e.date}}

    :tags: {{e.tag_text}}
    
{% endfor %}

Tag Index
=========
{% for t in tags %}
*   {{t}}
    {% for post in tags[t] %}
    -   `{{post.title}}`_
    {%- endfor %}
{% endfor %}
"""
)

test_jinja_temple_render = """
    >>> print(blog_template.render(tags=travel.by_tag(), **travel.as_dict()))
    Travel
    ======
    <BLANKLINE>
    <BLANKLINE>
        Hard Aground
        ------------
    <BLANKLINE>
        Some embarrassing revelation. Including ☹ and ⚓︎
    <BLANKLINE>
        :date: 2013-11-14 17:25:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #ICW
    <BLANKLINE>
    <BLANKLINE>
        Anchor Follies
        --------------
    <BLANKLINE>
        Some witty epigram. Including < & > characters.
    <BLANKLINE>
        :date: 2013-11-18 15:30:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #Mistakes
    <BLANKLINE>
    <BLANKLINE>
    <BLANKLINE>
    Tag Index
    =========
    <BLANKLINE>
    *   #RedRanger
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #Whitby42
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #ICW
    <BLANKLINE>
        -   `Hard Aground`_
    <BLANKLINE>
    *   #Mistakes
    <BLANKLINE>
        -   `Anchor Follies`_
    <BLANKLINE>
"""

# Example 4. JSON: Refactoring Encoding
# ######################################

# Changes to the class definitions to add a ``_json`` method.


class Post_J(Post):
    """Not really essential to inherit from Post, it's simply a dataclass."""
    @property
    def _json(self) -> Dict[str, Any]:
        return dict(
            __class__=self.__class__.__name__,
            __kw__=dict(
                date=self.date, title=self.title, rst_text=self.rst_text, tags=self.tags
            ),
            __args__=[],
        )

class Blog_J(Blog):
    """Note. No explicit reference to Blog_J for entries."""

    @property
    def _json(self) -> Dict[str, Any]:
        return dict(
            __class__=self.__class__.__name__,
            __kw__={},
            __args__=[self.title, self.entries],
        )

def blog_j_encode(object: Union[Blog_J, Post_J, Any]) -> Dict[str, Any]:
    if isinstance(object, datetime.datetime):
        return dict(
            __class__="datetime.datetime",
            __args__=[],
            __kw__=dict(
                year=object.year,
                month=object.month,
                day=object.day,
                hour=object.hour,
                minute=object.minute,
                second=object.second,
            ),
        )
    else:
        try:
            encoding = object._json
        except AttributeError:
            encoding = json.JSONEncoder().default(object)
        return encoding


travel3 = Blog_J("Travel")
travel3.append(
    Post_J(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )
)
travel3.append(
    Post_J(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram.""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
)

test_json_4 = """
    >>> text = json.dumps(travel3, indent=4, default=blog_j_encode)
    >>> print(text)
    {
        "__class__": "Blog_J",
        "__kw__": {},
        "__args__": [
            "Travel",
            [
                {
                    "__class__": "Post_J",
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime",
                            "__args__": [],
                            "__kw__": {
                                "year": 2013,
                                "month": 11,
                                "day": 14,
                                "hour": 17,
                                "minute": 25,
                                "second": 0
                            }
                        },
                        "title": "Hard Aground",
                        "rst_text": "Some embarrassing revelation. Including \u2639 and \u2693",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#ICW"
                        ]
                    },
                    "__args__": []
                },
                {
                    "__class__": "Post_J",
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime",
                            "__args__": [],
                            "__kw__": {
                                "year": 2013,
                                "month": 11,
                                "day": 18,
                                "hour": 15,
                                "minute": 30,
                                "second": 0
                            }
                        },
                        "title": "Anchor Follies",
                        "rst_text": "Some witty epigram.",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#Mistakes"
                        ]
                    },
                    "__args__": []
                }
            ]
        ]
    }
"""

# Example 5: JSON: Super-Flexible Date Encoding
# #############################################

# Right at the edge of the envelope for dates. This may be too much flexibility.
# There's an ISO standard for dates, and using it is simpler.

# For other unique data objects, however, this kind of pattern may be helpful
# for providing a way to parse complex strings.

# Changes to the class definitions
def blog_j2_encode(object: Union[Blog_J, Post_J, Any]) -> Dict[str, Any]:
    if isinstance(object, datetime.datetime):
        return dict(
            __class__="datetime.datetime.strptime",
            __args__=[object.strftime("%Y-%m-%dT%H:%M:%S"), "%Y-%m-%dT%H:%M:%S"],
            __kw__={},
        )
    else:
        try:
            encoding = object._json
        except AttributeError:
            encoding = json.JSONEncoder().default(object)
        return encoding


test_json_5 = """
    >>> text = json.dumps(travel3, indent=4, default=blog_j2_encode)
    >>> print(text)
    {
        "__class__": "Blog_J",
        "__kw__": {},
        "__args__": [
            "Travel",
            [
                {
                    "__class__": "Post_J",
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime.strptime",
                            "__args__": [
                                "2013-11-14T17:25:00",
                                "%Y-%m-%dT%H:%M:%S"
                            ],
                            "__kw__": {}
                        },
                        "title": "Hard Aground",
                        "rst_text": "Some embarrassing revelation. Including \u2639 and \u2693",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#ICW"
                        ]
                    },
                    "__args__": []
                },
                {
                    "__class__": "Post_J",
                    "__kw__": {
                        "date": {
                            "__class__": "datetime.datetime.strptime",
                            "__args__": [
                                "2013-11-18T15:30:00",
                                "%Y-%m-%dT%H:%M:%S"
                            ],
                            "__kw__": {}
                        },
                        "title": "Anchor Follies",
                        "rst_text": "Some witty epigram.",
                        "tags": [
                            "#RedRanger",
                            "#Whitby42",
                            "#Mistakes"
                        ]
                    },
                    "__args__": []
                }
            ]
        ]
    }
    
    >>> from pprint import pprint
    >>> copy = json.loads(text, object_hook=blog_decode)
    >>> print(copy.title)
    Travel
    >>> pprint(copy.entries)
    [Post_J(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓', tags=['#RedRanger', '#Whitby42', '#ICW']),
     Post_J(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram.', tags=['#RedRanger', '#Whitby42', '#Mistakes'])]
"""

with (Path.cwd()/"data"/"ch10.json").open("w", encoding="UTF-8") as target:
    json.dump(travel3, target, separators=(",", ":"), default=blog_j2_encode)

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 2. YAML. Base Definitions
"""

# Persistence Classes
# ========================================

from typing import List, Optional, Dict, Any

# Example 2: Cards
# ###################

from enum import Enum
class Suit(str, Enum):
    Clubs = "♣"
    Diamonds = "♦"
    Hearts = "♥"
    Spades = "♠"

class Card:

    def __init__(self, rank: str, suit: Suit, hard: Optional[int]=None, soft: Optional[int]=None) -> None:
        self.rank = rank
        self.suit = suit
        self.hard = hard or int(rank)
        self.soft = soft or int(rank)

    def __str__(self) -> str:
        return f"{self.rank!s}{self.suit.value!s}"


class AceCard(Card):

    def __init__(self, rank: str, suit: Suit) -> None:
        super().__init__(rank, suit, 1, 11)


class FaceCard(Card):

    def __init__(self, rank: str, suit: Suit) -> None:
        super().__init__(rank, suit, 10, 10)



__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex2a.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 2. YAML (part a)
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
from pathlib import Path

from Chapter_10.ch10_ex1 import Post, Blog, travel, rst_render
from Chapter_10.ch10_ex2 import Suit, Card, FaceCard, AceCard

# YAML
# ===================

import yaml

# Example 1: That's it.
# ######################

# Start with original definitions

test_yaml = """
    >>> text = yaml.dump(travel)
    >>> print(text)
    !!python/object:Chapter_10.ch10_ex1.Blog
    entries:
    - !!python/object:Chapter_10.ch10_ex1.Post
      date: 2013-11-14 17:25:00
      rst_text: "Some embarrassing revelation. Including \\u2639 and \\u2693\\uFE0E"
      tags:
      - '#RedRanger'
      - '#Whitby42'
      - '#ICW'
      title: Hard Aground
    - !!python/object:Chapter_10.ch10_ex1.Post
      date: 2013-11-18 15:30:00
      rst_text: Some witty epigram. Including < & > characters.
      tags:
      - '#RedRanger'
      - '#Whitby42'
      - '#Mistakes'
      title: Anchor Follies
    title: Travel
    <BLANKLINE>
    
    >>> copy = yaml.load(text)
    >>> print(type(copy), copy.title)
    <class 'Chapter_10.ch10_ex1.Blog'> Travel
    >>> for p in copy.entries:
    ...        print(p.date.year, p.date.month, p.date.day, p.title, p.tags)
    2013 11 14 Hard Aground ['#RedRanger', '#Whitby42', '#ICW']
    2013 11 18 Anchor Follies ['#RedRanger', '#Whitby42', '#Mistakes']

    >>> text2 = yaml.dump(travel, allow_unicode=True)
    >>> print(text2)
    !!python/object:Chapter_10.ch10_ex1.Blog
    entries:
    - !!python/object:Chapter_10.ch10_ex1.Post
      date: 2013-11-14 17:25:00
      rst_text: Some embarrassing revelation. Including ☹ and ⚓︎
      tags:
      - '#RedRanger'
      - '#Whitby42'
      - '#ICW'
      title: Hard Aground
    - !!python/object:Chapter_10.ch10_ex1.Post
      date: 2013-11-18 15:30:00
      rst_text: Some witty epigram. Including < & > characters.
      tags:
      - '#RedRanger'
      - '#Whitby42'
      - '#Mistakes'
      title: Anchor Follies
    title: Travel
    <BLANKLINE>

"""

with (Path.cwd()/"data"/"ch10.yaml").open("w", encoding="UTF-8") as target:
    yaml.dump(travel, target)

# Example 2: Cards
# ###################


deck = [AceCard("A", Suit.Clubs), Card("2", Suit.Hearts), FaceCard("K", Suit.Diamonds)]

test_yaml_dump = """
    >>> text = yaml.dump(deck, allow_unicode=True)
    >>> print(text)
    - !!python/object:Chapter_10.ch10_ex2.AceCard
      hard: 1
      rank: A
      soft: 11
      suit: !!python/object/apply:Chapter_10.ch10_ex2.Suit
      - ♣
    - !!python/object:Chapter_10.ch10_ex2.Card
      hard: 2
      rank: '2'
      soft: 2
      suit: !!python/object/apply:Chapter_10.ch10_ex2.Suit
      - ♥
    - !!python/object:Chapter_10.ch10_ex2.FaceCard
      hard: 10
      rank: K
      soft: 10
      suit: !!python/object/apply:Chapter_10.ch10_ex2.Suit
      - ♦
    <BLANKLINE>    
"""


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex2b.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 2. YAML (part b)
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
from pathlib import Path
from Chapter_10.ch10_ex2 import Suit, Card, AceCard, FaceCard

# YAML -- 2b cards with custom representations
# =============================================

deck = [AceCard("A", Suit.Clubs), Card("2", Suit.Hearts), FaceCard("K", Suit.Diamonds)]

import yaml


def card_representer(dumper: Any, card: Card) -> str:
    return dumper.represent_scalar(
        "!Card", f"{card.rank!s}{card.suit.value!s}")


def acecard_representer(dumper: Any, card: Card) -> str:
    return dumper.represent_scalar(
        "!AceCard", f"{card.rank!s}{card.suit.value!s}")


def facecard_representer(dumper: Any, card: Card) -> str:
    return dumper.represent_scalar(
        "!FaceCard", f"{card.rank!s}{card.suit.value!s}")


def card_constructor(loader: Any, node: Any) -> Card:
    value = loader.construct_scalar(node)
    rank, suit = value[:-1], value[-1]
    return Card(rank, Suit(suit))


def acecard_constructor(loader: Any, node: Any) -> Card:
    value = loader.construct_scalar(node)
    rank, suit = value[:-1], value[-1]
    return AceCard(rank, Suit(suit))


def facecard_constructor(loader: Any, node: Any) -> Card:
    value = loader.construct_scalar(node)
    rank, suit = value[:-1], value[-1]
    return FaceCard(rank, Suit(suit))

# Changes to the yaml module will apply throughout the application.
# And this test run, also.
# We can also add this

yaml.add_representer(Card, card_representer)
yaml.add_representer(AceCard, acecard_representer)
yaml.add_representer(FaceCard, facecard_representer)
yaml.add_constructor("!Card", card_constructor)
yaml.add_constructor("!AceCard", acecard_constructor)
yaml.add_constructor("!FaceCard", facecard_constructor)

test_yaml_dump_load = """
    >>> print(*map(str, deck))
    A♣ 2♥ K♦
    
    >>> text = yaml.dump(deck, allow_unicode=True)
    >>> print(text)
    - !AceCard 'A♣'
    - !Card '2♥'
    - !FaceCard 'K♦'
    <BLANKLINE>
    
    >>> copy = yaml.load(text, Loader=yaml.Loader)
    >>> print(*map(str, copy))
    A♣ 2♥ K♦
"""


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex2c.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 2. YAML (part c)
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
from pathlib import Path
from Chapter_10.ch10_ex2 import Suit, Card, AceCard, FaceCard
from Chapter_10.ch10_ex2b import facecard_representer, acecard_representer, card_representer

# YAML -- 2c cards with safe custom representations
# ==================================================

import yaml


class Card2(yaml.YAMLObject):
    yaml_tag = "!Card2"
    yaml_loader = yaml.SafeLoader

    def __init__(self, rank, suit, hard=None, soft=None) -> None:
        self.rank = rank
        self.suit = suit
        self.hard = hard or int(rank)
        self.soft = soft or int(rank)

    def __str__(self) -> str:
        return "{0.rank!s}{0.suit!s}".format(self)


class AceCard2(Card2):
    yaml_tag = "!AceCard2"

    def __init__(self, rank, suit) -> None:
        super().__init__(rank, suit, 1, 11)


class FaceCard2(Card2):
    yaml_tag = "!FaceCard2"

    def __init__(self, rank, suit) -> None:
        super().__init__(rank, suit, 10, 10)


deck2 = [AceCard2("A", "♣"), Card2("2", "♥"), FaceCard2("K", "♦")]

test_yaml_dump_safe_load = """
    # Changes to the yaml module will apply throughout the application.
    >>> yaml.add_representer(Card, card_representer)
    >>> yaml.add_representer(AceCard, acecard_representer)
    >>> yaml.add_representer(FaceCard, facecard_representer)

    >>> text2 = yaml.dump(deck2)
    >>> print(text2)
    - !AceCard2
      hard: 1
      rank: A
      soft: 11
      suit: "\\u2663"
    - !Card2
      hard: 2
      rank: '2'
      soft: 2
      suit: "\\u2665"
    - !FaceCard2
      hard: 10
      rank: K
      soft: 10
      suit: "\\u2666"
    <BLANKLINE>
    
    >>> copy = yaml.safe_load(text2)
    >>> print([str(c) for c in copy])
    ['A♣', '2♥', 'K♦']
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex3.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 3. Pickle
"""

# Persistence Classes
# ========================================

import datetime
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
from pathlib import Path

from Chapter_10.ch10_ex1 import Post, Blog, travel, rst_render
from Chapter_10.ch10_ex2 import FaceCard, AceCard, Card, Suit

# Pickle
# ===================

# Example 1: Working
# ####################

# Use pickle to persist our microblog
import pickle
from pathlib import Path

test_pickle = """
    >>> with (Path.cwd()/"data"/"ch10_travel_blog.p").open("wb") as target:
    ...     pickle.dump(travel, target)

    >>> with(Path.cwd()/"data"/"ch10_travel_blog.p").open("rb") as source:
    ...     copy = pickle.load(source)

    >>> print(copy.title)
    Travel
    >>> for post in copy.entries:
    ...     print(post)
    Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW'])
    Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including < & > characters.', tags=['#RedRanger', '#Whitby42', '#Mistakes'])
"""

# Example 2: Won't Init
# ########################

import logging, sys

audit_log = logging.getLogger("audit")

class Hand_bad:

    def __init__(self, dealer_card: Card, *cards: Card) -> None:
        self.dealer_card = dealer_card
        self.cards = list(cards)
        for c in self.cards:
            audit_log.info("Initial %s", c)

    def append(self, card: Card) -> None:
        self.cards.append(card)
        audit_log.info("Hit %s", card)

    def __str__(self) -> str:
        cards = ", ".join(map(str, self.cards))
        return f"{self.dealer_card} | {cards}"


test_audit = """
    >>> logging.basicConfig(stream=sys.stderr, level=logging.INFO)

    >>> logging.info("bad create")
    >>> h = Hand_bad(FaceCard("K", Suit.Diamonds), AceCard("A", Suit.Clubs), Card("9", Suit.Hearts))
    >>> print(h)
    K♦ | A♣, 9♥

    >>> b = pickle.dumps(h)

    >>> logging.info("bad load from pickle")
    >>> h2 = pickle.loads(b)
    >>> print(h2)
    K♦ | A♣, 9♥
    
    >>> logging.shutdown()
"""


class Hand2:

    def __init__(self, dealer_card: Card, *cards: Card) -> None:
        self.dealer_card = dealer_card
        self.cards = list(cards)
        for c in self.cards:
            audit_log.info("Initial %s", c)

    def append(self, card: Card) -> None:
        self.cards.append(card)
        audit_log.info("Hit %s", card)

    def __str__(self) -> str:
        cards = ", ".join(map(str, self.cards))
        return f"{self.dealer_card} | {cards}"

    def __getstate__(self) -> Dict[str, Any]:
        return vars(self)

    def __setstate__(self, state: Dict[str, Any]) -> None:
        # Not very secure -- hard for mypy to detect what's going on.
        self.__dict__.update(state)
        for c in self.cards:
            audit_log.info("Initial (unpickle) %s", c)

test_audit_2 = """
    >>> logging.basicConfig(stream=sys.stderr, level=logging.INFO)

    >>> logging.info("good create")
    >>> hp = Hand2(FaceCard("K", Suit.Diamonds), AceCard("A", Suit.Clubs), Card("9", Suit.Hearts))

    >>> data = pickle.dumps(hp)

    >>> logging.info("good load from pickle")
    >>> h2p = pickle.loads(data)
    >>> print(h2p)
    K♦ | A♣, 9♥

    >>> logging.shutdown()
"""

# Example 3: Secure Pickle
# ########################

import builtins


class RestrictedUnpickler(pickle.Unpickler):

    def find_class(self, module: str, name: str) -> Any:
        if module == "builtins":
            if name not in ("exec", "eval"):
                return getattr(builtins, name)
        elif module in ("__main__", "Chapter_10.ch10_ex3", "ch10_ex3"):
            # Valid module names depends on execution context.
            return globals()[name]
        # elif module in any of our application modules...
        elif module in ("Chapter_10.ch10_ex2",):
            return globals()[name]
        raise pickle.UnpicklingError(
            f"global '{module}.{name}' is forbidden"
        )


test_audit_3 = """
    >>> import io
    >>> logging.basicConfig(stream=sys.stderr, level=logging.INFO)

    >>> hp = Hand2(FaceCard("K", Suit.Diamonds), AceCard("A", Suit.Clubs), Card("9", Suit.Hearts))

    >>> data = pickle.dumps(hp)
    >>> try:
    ...     h2s = RestrictedUnpickler(io.BytesIO(data)).load()
    ... except pickle.UnpicklingError as e:
    ...     print(e)
    >>> print(h2s)
    K♦ | A♣, 9♥
    
    Creating an unimportable pickle file requires something not in Chapter_10.ch10_ex2.
    >>> from Chapter_10.ch10_ex1 import travel
    >>> bad_data = pickle.dumps(travel)
    >>> try:
    ...     travel_copy = RestrictedUnpickler(io.BytesIO(bad_data)).load()
    ... except pickle.UnpicklingError as e:
    ...     print(e)
    global 'Chapter_10.ch10_ex1.Blog' is forbidden
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex4.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 4. CSV
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from typing import List, Optional, Dict, Any, Iterator
from dataclasses import dataclass
from pathlib import Path

from Chapter_10.ch10_ex1 import Post, Blog, travel, rst_render
from Chapter_10.ch10_ex2 import FaceCard, AceCard, Card
import io

# CSV
# ===================

# Example 1: GameStats
# ######################


class Table:
    """Abstraction for games played on tables."""

    def bet(self, game_state: str, amount: float) -> None:
        """Accepts a bet for a particular future game state."""
        pass


class Player_Strategy:
    """Abstraction for player choices. Varies by game, of course."""
    pass


class Betting:

    def __init__(self, stake: float = 100) -> None:
        self.stake = stake

    def bet(self, table: Table, game_state: str) -> None:
        if game_state == "ante":
            table.bet(game_state, 1)

    def win(self, amount: float) -> None:
        self.stake += amount

    def loss(self, amount: float) -> None:
        self.stake -= amount

    def push(self) -> None:
        pass


class Flat_Bet(Betting):
    pass


class Martingale_Bet(Betting):

    def __init__(self, *args) -> None:
        super().__init__(*args)
        self.stage = 1

    def bet(self, table: Table, game_state: str) -> None:
        if game_state == "ante":
            try:
                table.bet(game_state, min(self.stage, self.stake))
            except BadBet as e:
                limit = e.args[0]
                table.bet(game_state, min(limit, self.stake))

    def win(self, amount) -> None:
        self.stage = 1
        super().win(amount)

    def loss(self, amount) -> None:
        self.stage = min(self.stage * 2, 512)
        super().loss(amount)

    def push(self) -> None:
        super().push()


import random


class BadBet(Exception):
    pass


class Broke(Exception):
    pass


# A "Table" implementation for Blackjack.
class Blackjack(Table):

    def __init__(self, play: Player_Strategy, betting: Betting) -> None:
        self.player = play
        self.betting = betting
        self.bets: Dict[str, float] = dict()
        self.rounds = 0

    @property
    def stake(self) -> float:
        return self.betting.stake

    def bet(self, game_state: str, amount: float) -> None:
        if amount > 50:
            raise BadBet(50)
        self.bets[game_state] = amount

    def play_1(self) -> None:
        if self.betting.stake == 0:
            raise Broke
        self.betting.bet(self, "ante")
        bet = sum(self.bets.values())
        outcome = random.random()
        if outcome < 0.579:
            self.betting.loss(bet)
        elif 0.579 <= outcome < 0.883:
            self.betting.win(bet)
        elif 0.883 <= outcome < 0.943:
            self.betting.push()
        else:
            # 0.943 <= outcome
            self.betting.win(bet * 2)

    def until_broke_or_rounds(self, limit: int) -> None:
        while self.rounds < limit and self.betting.stake > 0:
            self.play_1()
            self.rounds += 1


# Example 1 dumping
# ####################

# An application of the above definitions.
from typing import NamedTuple


class GameStat(NamedTuple):
    player: str
    bet: str
    rounds: int
    final: float


from typing import Iterator, Type


def gamestat_iter(
    player: Type[Player_Strategy], betting: Type[Betting], limit: int = 100
) -> Iterator[GameStat]:
    for sample in range(30):
        random.seed(sample)  # Reproducible
        b = Blackjack(player(), betting())
        b.until_broke_or_rounds(limit)
        yield GameStat(player.__name__, betting.__name__, b.rounds, b.betting.stake)


import csv
from pathlib import Path

with (Path.cwd() / "data" / "ch10_blackjack_1.csv").open("w", newline="") as target:
    writer = csv.DictWriter(target, GameStat._fields)
    writer.writeheader()
    for gamestat in gamestat_iter(Player_Strategy, Martingale_Bet):
        writer.writerow(gamestat._asdict())

data = gamestat_iter(Player_Strategy, Martingale_Bet)
with (Path.cwd() / "data" / "ch10_blackjack_2.csv").open("w", newline="") as target:
    writer = csv.DictWriter(target, GameStat._fields)
    writer.writeheader()
    writer.writerows(g._asdict() for g in data)

# Example 2 loading
# ###################

# Loading data from the simulator

with (Path.cwd() / "data" / "ch10_blackjack_1.csv").open() as source:
    reader = csv.DictReader(source)
    assert set(reader.fieldnames) == set(GameStat._fields)
    for gs in (GameStat(**r) for r in reader):
        pass  # print( gs )


def gamestat_rdr_iter(
        source_data: Iterator[Dict[str, str]]
    ) -> Iterator[GameStat]:
    for row in source_data:
        yield GameStat(row["player"], row["bet"], int(row["rounds"]), int(row["final"]))


test_write_read_1 = """
    >>> with (Path.cwd()/"data"/"ch10_blackjack_1.csv").open() as source:
    ...     reader = csv.DictReader(source)
    ...     assert set(reader.fieldnames) == set(GameStat._fields)
    ...     for gs in gamestat_rdr_iter(reader):
    ...         print(gs)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=142)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=27, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=25, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=157)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=87)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=18, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=161)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=10, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=22, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=53, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=37, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=27, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=188)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=58, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=103)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=28, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=60, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=150)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=9, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=13, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=97, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=100, final=93)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=72, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=12, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=36, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=35, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=78, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=68, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=39, final=0)
    GameStat(player='Player_Strategy', bet='Martingale_Bet', rounds=47, final=0)
"""

# Example 3 blog and post one file
# ################################

# There are two row types, however -- blogs and posts within a blog.

# Our blog data to be saved positionally.
blogs = [travel]

with (Path.cwd() / "data" / "ch10_blog3.csv").open("w", newline="") as target:
    wtr = csv.writer(target)
    wtr.writerow(["__class__", "title", "date", "title", "rst_text", "tags"])
    for b in blogs:
        wtr.writerow(["Blog", b.title, None, None, None, None])
        for p in b.entries:
            wtr.writerow(["Post", None, p.date, p.title, p.rst_text, p.tags])

# Super-important: column order must match __init__() param order.
# Hard to do in general.
# And impossible to make work with mypy unless your Blog and Post structures
# are reduced to List[str]
with (Path.cwd() / "data" / "ch10_blog3.csv").open() as source:
    rdr = csv.reader(source)
    header = next(rdr)
    assert header == ["__class__", "title", "date", "title", "rst_text", "tags"]
    blogs = []
    for r in rdr:
        if r[0] == "Blog":
            blog = Blog(*r[1:2])  # type: ignore
            blogs.append(blog)
        elif r[0] == "Post":
            post = Post(*r[2:])  # type: ignore
            blogs[-1].append(post)

# Tags, however, will not be a proper tuple
# The above doesn't handle Post tags properly!

# Can use the following for safe eval of literals.
import ast


def blog_builder(row: List[str]) -> Blog:
    return Blog(row[1])


def post_builder(row: List[str]) -> Post:
    return Post(
        date=datetime.datetime.strptime(row[2], "%Y-%m-%d %H:%M:%S"),
        title=row[3],
        rst_text=row[4],
        tags=ast.literal_eval(row[5]),
    )


with (Path.cwd() / "data" / "ch10_blog3.csv").open() as source:
    rdr = csv.reader(source)
    header = next(rdr)
    assert header == ["__class__", "title", "date", "title", "rst_text", "tags"]
    blogs = []
    for r in rdr:
        if r[0] == "Blog":
            blog = blog_builder(r)
            blogs.append(blog)
        elif r[0] == "Post":
            post = post_builder(r)
            blogs[-1].append(post)

# Example 4 blog and post with better metadata and filter
# ########################################################

# Loading the blog with a generator function.

from typing import TextIO, cast


def blog_iter(source: TextIO) -> Iterator[Blog]:
    rdr = csv.reader(source)
    header = next(rdr)
    assert header == ["__class__", "title", "date", "title", "rst_text", "tags"]
    blog: Blog = cast(Blog, None)
    for r in rdr:
        if r[0] == "Blog":
            if blog:
                yield blog
            blog = blog_builder(r)
        elif r[0] == "Post":
            post = post_builder(r)
            blog.append(post)
    if blog:
        yield blog


test_blog_3 = """
    >>> with (Path.cwd()/"data"/"ch10_blog3.csv").open() as source:
    ...     for b in blog_iter(source):
    ...         print(b.title, [p.title for p in b.entries])
    Travel ['Hard Aground', 'Anchor Follies']

    >>> with (Path.cwd()/"data"/"ch10_blog3.csv").open() as source:
    ...     blogs = list(blog_iter(source))
    >>> for b in blogs:
    ...     print(b.title, [p.title for p in b.entries])
    Travel ['Hard Aground', 'Anchor Follies']
"""

# Example 5 Blog and Post join
# ################################

# Using a "join" between Blog and Post to create a file.
with (Path.cwd() / "data" / "ch10_blog5.csv").open("w", newline="") as target:
    wtr = csv.writer(target)
    wtr.writerow(
        ["Blog.title", "Post.date", "Post.title", "Post.tags", "Post.rst_text"]
    )
    for b in blogs:
        for p in b.entries:
            wtr.writerow([b.title, p.date, p.title, p.tags, p.rst_text])

from typing import Union, Iterator, Tuple


import ast


def post_builder5(row: Dict[str, str]) -> Post:
    return Post(
        date=datetime.datetime.strptime(row["Post.date"], "%Y-%m-%d %H:%M:%S"),
        title=row["Post.title"],
        rst_text=row["Post.rst_text"],
        tags=ast.literal_eval(row["Post.tags"]),
    )


def blog_builder5(row: Dict[str, str]) -> Blog:
    return Blog(title=row["Blog.title"])


from typing import TextIO


def blog_iter2(source: TextIO) -> Iterator[Blog]:
    """An iterator which fetches blogs"""

    rdr = csv.DictReader(source)
    assert (
        set(rdr.fieldnames)
        == {"Blog.title", "Post.date", "Post.title", "Post.tags", "Post.rst_text"}
    )
    # Fetch the first row and build the first Blog and Post from this
    row = next(rdr)
    blog = blog_builder5(row)
    post = post_builder5(row)
    blog.append(post)

    # Fetch all subsequent rows.
    # Emit completed Blogs.
    # Append Posts to the currently open Blog
    for row in rdr:
        if row["Blog.title"] != blog.title:
            yield blog
            blog = blog_builder5(row)
        post = post_builder5(row)
        blog.append(post)
    yield blog


test_blog_iter_2 = """
    >>> with (Path.cwd()/"data"/"ch10_blog5.csv").open() as source:
    ...    for b in blog_iter2(source):
    ...        print(b.title, b.as_dict())
    Travel {'title': 'Travel', 'underline': '======', 'entries': [{'date': '2013-11-14 17:25:00', 'title': 'Hard Aground', 'underline': '------------', 'rst_text': 'Some embarrassing revelation. Including ☹ and ⚓︎', 'tag_text': '#RedRanger #Whitby42 #ICW'}, {'date': '2013-11-18 15:30:00', 'title': 'Anchor Follies', 'underline': '--------------', 'rst_text': 'Some witty epigram. Including < & > characters.', 'tag_text': '#RedRanger #Whitby42 #Mistakes'}]}
        
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex5.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 5. EBCDIC encode/decode
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from typing import List, Optional, Dict, Any, Tuple, Callable, Union
from dataclasses import dataclass
from pathlib import Path

from Chapter_10.ch10_ex1 import Post, Blog, travel, rst_render
from Chapter_10.ch10_ex2 import FaceCard, AceCard, Card
from Chapter_10.ch10_ex4 import Player_Strategy, Martingale_Bet, gamestat_iter, GameStat
import io

# Legacy Files
# ===================

# We'll look at pure text and mixed text w/ packed decimal.

# Example 1 dumping all text
# ###########################

# Metadata for Gamestat objects.
# attribute name, start, size, and an output format specification.

from typing import NamedTuple, BinaryIO, TextIO, Iterable, Iterator, cast
from pathlib import Path

class FixedField(NamedTuple):
    name: str
    offset: int
    length: int
    format_spec: str

class Metadata(NamedTuple):
    fields: List[FixedField]
    reclen: int

metadata_txt = Metadata(
    fields=[
        FixedField("player", 0, 20, "{:<{size}s}"),
        FixedField("bet", 20, 20, "{:<{size}s}"),
        FixedField("rounds", 40, 5, "{:>{size}d}"),
        FixedField("final", 45, 8, "{:>{size}d}"),
    ],
    reclen=53,
)

# A function to transform a namedtuple into a fixed-layout record.
def gamestat_record(gamestat:GameStat, metadata: Metadata) -> str:
    record_fields = [
        format_spec.format(getattr(gamestat, name), size=size)
        for name, start, size, format_spec in metadata.fields
    ]
    record_text = "".join(record_fields)
    assert len(record_text) == metadata.reclen, f"Got {len(record_text)} Should Be {metadata.reclen}"
    return record_text


# An application of the game statistics definitions.
with (Path.cwd()/"data"/"ch10_blackjack.file").open("w", encoding="cp037", newline="") as target:
    for gamestat in gamestat_iter(Player_Strategy, Martingale_Bet):
        record = gamestat_record(gamestat, metadata_txt)
        target.write(record)

# Example 2 loading all text
# ##########################

# Loading data from the simulator. Part 1 -- Physical decomposition into rows.
def line_iter(aFile: TextIO, metadata: Union[Metadata, 'XMetadata']) -> Iterator[str]:
    recBytes = aFile.read(metadata.reclen)
    while recBytes:
        yield recBytes
        recBytes = aFile.read(metadata.reclen)


# Part 2 -- decomposition into named fields.
def record_iter(aFile: TextIO, metadata: Metadata) -> Iterator[Dict[str, str]]:
    for line in line_iter(aFile, metadata):
        record = {
            name: line[start:start + size].strip()
            for name, start, size, format_spec in metadata.fields
        }
        yield record


# Part 3 -- using the field to dictionary parser.
test_reader_1 = """
    >>> with (Path.cwd()/"data"/"ch10_blackjack.file").open("r", encoding="cp037", newline="") as source:
    ...     for record_in in record_iter(cast(TextIO, source), metadata_txt):
    ...         print(record_in)
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '142'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '27', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '25', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '157'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '87'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '18', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '161'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '10', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '22', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '53', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '37', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '27', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '188'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '58', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '103'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '28', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '60', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '150'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '9', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '13', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '97', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '100', 'final': '93'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '72', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '12', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '36', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '35', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '78', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '68', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '39', 'final': '0'}
    {'player': 'Player_Strategy', 'bet': 'Martingale_Bet', 'rounds': '47', 'final': '0'}
"""

# Example 3 -- USAGE DISPLAY and USAGE COMP3
# ###########################################

# Using COMP-3 expands the problem into three kinds of data
#
# - Alpha and Alphanumeric encoded in EBCDIC or ASCII
#
# - Numeric, USAGE DISPLAY, as a string of digits encoded in EBCDIC or ASCII
#
# - Numeric, USAGE COMP-3, as string of bytes encoded as packed decimal.
#
# All of which require the decimal module's Decimal class definition.

from decimal import Decimal

# As a convenience, we map 'ebcdic' to 'cp037' by adding a new lookup function.
#
import codecs


def ebcdic_lookup(name, fallback=codecs.lookup):  # real signature unknown
    if name == "ebcdic":
        return codecs.lookup("cp037")
    return fallback(name)


codecs.register(ebcdic_lookup)

# Alphanumeric USAGE DISPLAY conversion.
# The COBOL program stored text.
def alpha_decode(data: bytes, metadata: 'XMetadata', field_metadata: 'XField') -> str:
    """Decode alpha or alphanumeric data.
    metadata has encoding.
    field_metadata is (currently) not used.

    Mock metadata objects
    >>> import types
    >>> meta = types.SimpleNamespace(reclen=6, encoding='ebcdic')
    >>> meta.decode = codecs.getdecoder(meta.encoding)
    >>> field_meta = types.SimpleNamespace()  # Used in other examples...

    >>> data = bytes([0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0x60])
    >>> alpha_decode(data, meta, field_meta)
    '98765-'

    """
    text, _ = metadata.decode(data)
    return text


# Numeric USAGE DISPLAY trailing sign conversion.
# The COBOL program stored text version of the number.
def display_decode(data: bytes, metadata: 'XMetadata', field_metadata: 'XField') -> Decimal:
    """Decode USAGE DISPLAY numeric data.
    metadata has encoding.
    field_metadata has attributes name, start, size, format, precision, usage.

    Mock metadata objects
    >>> import types
    >>> meta= types.SimpleNamespace(reclen=6, encoding='ebcdic')
    >>> meta.decode = codecs.getdecoder(meta.encoding)
    >>> field_meta = types.SimpleNamespace(precision=2)

    >>> data = bytes([0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0x60])
    >>> display_decode(data, meta, field_meta)
    Decimal('-987.65')

    """
    text, _ = metadata.decode(data)
    precision = field_metadata.precision or 0  # If None, default is 0.
    text, sign = text[:-1], text[-1]
    return Decimal(sign + text[:-precision] + "." + text[-precision:])


# Numeric USAGE COMP-3 conversion.
# The COBOL program encoded the number into packed decimal representation.
def comp3_decode(data: bytes, metadata: 'XMetadata', field_metadata: 'XField') -> Decimal:
    """Decode USAGE COMP-3 data.
    metadata has encoding, which is not used.
    field_metadata has attributes name, start, size, format, precision, usage.

    Note that the size is the overall resulting string of bytes.
    NOT the number of digits involved.

    Mock metadata objects
    >>> import types
    >>> meta = types.SimpleNamespace() # Not used
    >>> field_meta = types.SimpleNamespace(precision=2)

    >>> data = bytes((0x98, 0x76, 0x5d))
    >>> comp3_decode(data, meta, field_meta)
    Decimal('-987.65')

    """
    precision = field_metadata.precision or 0  # Default when precision is omitted
    digits = []
    for b in data[:-1]:
        hi, lo = divmod(b, 16)
        digits.append(str(hi))
        digits.append(str(lo))
    digit, sign_byte = divmod(data[-1], 16)
    digits.append(str(digit))
    text = "".join(digits)
    sign = "-" if sign_byte in (0x0b, 0x0d) else "+"
    return Decimal(sign + text[:-precision] + "." + text[-precision:])


# Encoder for simple alpha or alphanumeric.
def alpha_encode(data: Any, metadata: 'XMetadata', field_metadata: 'XField') -> bytes:
    """Encode alpha or alphanumeric data.
    metadata has encoding.
    field_metadata is not used.

    Mock metadata objects
    >>> import types
    >>> meta = types.SimpleNamespace(encoding='ebcdic')
    >>> meta.encode = codecs.getencoder(meta.encoding)
    >>> field_meta = types.SimpleNamespace(length=6)

    >>> data = '98765-'
    >>> actual = alpha_encode(data, meta, field_meta)
    >>> repr(actual)
    "b'\\\\xf9\\\\xf8\\\\xf7\\\\xf6\\\\xf5`'"
    >>> actual == bytes([0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0x60])
    True

    """
    bytes, _ = metadata.encode("{:<{size}s}".format(data, size=field_metadata.length))
    return bytes


# Encoder for numeric USAGE DISPLAY, trailing sign.
def display_encode(data: Decimal, metadata: 'XMetadata', field_metadata: 'XField') -> bytes:
    """Encode numeric USAGE DISPLAY trailing sign.
    metadata has encoding.
    field_metadata has attributes name, start, size, format, precision, usage.

    Mock metadata objects
    >>> import types, decimal
    >>> meta = types.SimpleNamespace(encoding='ebcdic')
    >>> meta.encode = codecs.getencoder(meta.encoding)
    >>> field_meta = types.SimpleNamespace(length=6, precision=2)

    >>> actual = display_encode(Decimal('-987.65'), meta, field_meta)
    >>> repr(actual)
    "b'\\\\xf9\\\\xf8\\\\xf7\\\\xf6\\\\xf5`'"
    >>> actual ==  bytes([0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0x60])
    True

    """
    precision = field_metadata.precision or 0
    text = "{0:0>{size}d}{1}".format(
        abs(int(data * Decimal(10) ** precision)),
        "-" if data < 0 else "+",
        size=field_metadata.length - 1,
    )
    bytes, _ = metadata.encode(text)
    return bytes


# Encoder for numeric USAGE COMP-3.
def comp3_encode(data: Decimal, metadata: 'XMetadata', field_metadata: 'XField') -> bytes:
    """Encode numeric USAGE COMP-3.
    metadata has encoding which is not used.
    field_metadata has attributes name, start, size, format, precision, usage.

    Note that the size is the overall resulting string of bytes.
    NOT the number of digits involved.
    This has 2 digits per byte + a digit and a sign.

    Mock metadata objects
    >>> import types
    >>> meta = types.SimpleNamespace(encoding='ebcdic')
    >>> field_meta = types.SimpleNamespace(length=3, precision=2)

    >>> actual = comp3_encode(Decimal('-987.65'), meta, field_meta)
    >>> repr(actual)
    "b'\\\\x98v]'"
    >>> actual == bytes((0x98, 0x76, 0x5d))
    True

    """
    precision = field_metadata.precision or 0
    value = abs(int(data * Decimal(10) ** precision))
    digits = [0x0d if data < 0 else 0x00]  # Trailing sign.
    nDigits = field_metadata.length * 2 - 1
    for i in range(nDigits):
        digits = [value % 10] + digits
        value //= 10
    b = bytes((hi * 16 + lo for hi, lo in list(zip(digits[::2], digits[1::2]))))
    return b


# Our expanded metadata to include more refined field-level definitions.
# First, we'll define some encode-decode pairs.
alphanumeric = (alpha_encode, alpha_decode)
usage_display = (display_encode, display_decode)
usage_comp3 = (comp3_encode, comp3_decode)

Encoder = Callable[[Any, Any, Any], bytes]
Decoder = Callable[[bytes, Any, Any], Any]
Usage = Tuple[Encoder, Decoder]

# Then we'll define a more sophisticated metadata that includes the
# precision and a reference to the relevant encode-decode pair.
#
# The overall metadata encoding name is transformed into an
# encode and decode function to save lookups on a field-by-field basis.
import collections

class XField(NamedTuple):
    name: str
    offset: int
    length: int
    precision: Optional[int]
    usage: Tuple[Callable, Callable]

class XMetadata(NamedTuple):
    fields: List[XField]
    reclen: int
    encoding: str

    @property
    def decode(self) -> Callable[[bytes], Tuple[str, int]]:
        return codecs.getdecoder(self.encoding)

    @property
    def encode(self) -> Callable[[str], Tuple[bytes, int]]:
        return codecs.getencoder(self.encoding)


metadata_comp3 = XMetadata(
    fields=[
        XField("player", 0, 20, None, alphanumeric),
        XField("bet", 20, 20, None, alphanumeric),
        XField("rounds", 40, 8, 2, usage_display),
        XField("final", 48, 8, 2, usage_comp3),
    ],
    reclen=56,
    encoding="ebcdic",  # for display fields and alphanumeric fields.
)

# A function to transform a namedtuple into a fixed-layout record.
def gamestat_record_comp3(gamestat: GameStat, metadata: XMetadata) -> bytes:
    record = [
        field.usage[0](getattr(gamestat, field.name), metadata, field)
        for field in metadata.fields
    ]
    text = b"".join(record)
    assert len(text) == metadata.reclen, "Got {0} != Should Be {1}".format(
        len(text), metadata.reclen
    )
    return text


# Example encoding app.
with (Path.cwd()/"data"/"ch10_blackjack_comp3.file").open("wb") as target:
    for gamestat in gamestat_iter(Player_Strategy, Martingale_Bet):
        data_bytes = gamestat_record_comp3(gamestat, metadata_comp3)
        target.write(data_bytes)

# Example decoding iterator using more sophisticated metadata.
def record2_iter(aFile: TextIO, metadata: XMetadata) -> Iterator[Dict[str, XField]]:
    for line in line_iter(aFile, metadata):
        field_data = (
            (field, line[field.offset:field.offset + field.length])
            for field in metadata.fields
        )
        record = dict(
            (field.name, field.usage[1](data, metadata, field))
            for field, data in field_data
        )
        yield record

test_reader_2 = """
    >>> with (Path.cwd()/"data"/"ch10_blackjack_comp3.file").open("rb") as source:
    ...     for record in record2_iter(source, metadata_comp3):
    ...        print(record)
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('142.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('27.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('25.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('157.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('87.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('18.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('161.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('10.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('22.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('53.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('37.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('27.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('188.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('58.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('103.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('28.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('60.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('150.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('9.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('13.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('97.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('100.00'), 'final': Decimal('93.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('72.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('12.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('36.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('35.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('78.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('68.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('39.00'), 'final': Decimal('0.00')}
    {'player': 'Player_Strategy     ', 'bet': 'Martingale_Bet      ', 'rounds': Decimal('47.00'), 'final': Decimal('0.00')}

"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_10/ch10_ex6.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 10. Example 6. XML
"""

# Persistence Classes
# ========================================

# A detail class for micro-blog posts
import datetime
from dataclasses import dataclass, field, asdict
from typing import List, DefaultDict, Dict, Any
from collections import defaultdict
import io
from Chapter_10.ch10_ex1 import travel, rst_render


# XML
# ===================

# Example 1: XML output
# ######################

from dataclasses import dataclass, field, asdict
@dataclass
class Post_X:
    date: datetime.datetime
    title: str
    rst_text: str
    tags: List[str]
    underline: str = field(init=False)
    tag_text: str = field(init=False)

    def __post_init__(self) -> None:
        self.underline = "-"*len(self.title)
        self.tag_text = ' '.join(self.tags)

    def as_dict(self) -> Dict[str, Any]:
        return asdict(self)

    def xml(self) -> str:
        tags = "".join(f"<tag>{t}</tag>" for t in self.tags)
        return f"""\
<entry>
    <title>{self.title}</title>
    <date>{self.date}</date>
    <tags>{tags}</tags>
    <text>{self.rst_text}</text>
</entry>"""


from dataclasses import dataclass, field, asdict

@dataclass
class Blog_X:
    title: str
    entries: List[Post_X] = field(default_factory=list)
    underline: str = field(init=False)

    def __post_init__(self) -> None:
        self.underline = "="*len(self.title)

    def append(self, post: Post_X) -> None:
        self.entries.append(post)

    def by_tag(self) -> DefaultDict[str, List[Dict[str, Any]]]:
        tag_index: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
        for post in self.entries:
            for tag in post.tags:
                tag_index[tag].append(asdict(post))
        return tag_index

    def as_dict(self) -> Dict[str, Any]:
        return asdict(self)

    def xml(self) -> str:
        children = "\n".join(c.xml() for c in self.entries)
        return f"""\
<blog><title>{self.title}</title>
<entries>
{children}
<entries>
</blog>
"""


travel4 = Blog_X("Travel")
travel4.append(
    Post_X(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )
)
travel4.append(
    Post_X(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram.""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
)

test_xml_out_1 = """
    >>> print(travel4.xml())  # doctest: +NORMALIZE_WHITESPACE
    <blog><title>Travel</title>
    <entries>
    <entry>
        <title>Hard Aground</title>
        <date>2013-11-14 17:25:00</date>
        <tags><tag>#RedRanger</tag><tag>#Whitby42</tag><tag>#ICW</tag></tags>
        <text>Some embarrassing revelation. Including ☹ and ⚓</text>
    </entry>
    <entry>
        <title>Anchor Follies</title>
        <date>2013-11-18 15:30:00</date>
        <tags><tag>#RedRanger</tag><tag>#Whitby42</tag><tag>#Mistakes</tag></tags>
        <text>Some witty epigram.</text>
    </entry>
    <entries>
    </blog>
"""

# Example 2: element Tree output
# ##############################

import xml.etree.ElementTree as XML
from typing import cast

class Blog_E(Blog_X):

    def xmlelt(self) -> XML.Element:
        blog = XML.Element("blog")
        title = XML.SubElement(blog, "title")
        title.text = self.title
        title.tail = "\n"
        entities = XML.SubElement(blog, "entries")
        entities.extend(cast('Post_E', c).xmlelt() for c in self.entries)
        blog.tail = "\n"
        return blog


class Post_E(Post_X):

    def xmlelt(self) -> XML.Element:
        post = XML.Element("entry")
        title = XML.SubElement(post, "title")
        title.text = self.title
        date = XML.SubElement(post, "date")
        date.text = str(self.date)
        tags = XML.SubElement(post, "tags")
        for t in self.tags:
            tag = XML.SubElement(tags, "tag")
            tag.text = t
        text = XML.SubElement(post, "rst_text")
        text.text = self.rst_text
        post.tail = "\n"
        return post


travel5 = Blog_E("Travel")
travel5.append(
    Post_E(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )
)
travel5.append(
    Post_E(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including < & > characters.""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
)

test_xml_out_2 = """
    >>> tree = XML.ElementTree(travel5.xmlelt())
    >>> text = XML.tostring(tree.getroot())
    >>> print(text.decode('utf-8'))  # doctest: +NORMALIZE_WHITESPACE
    <blog><title>Travel</title>
    <entries><entry><title>Hard Aground</title><date>2013-11-14 17:25:00</date><tags><tag>#RedRanger</tag><tag>#Whitby42</tag><tag>#ICW</tag></tags><rst_text>Some embarrassing revelation. Including &#9785; and &#9875;</rst_text></entry>
    <entry><title>Anchor Follies</title><date>2013-11-18 15:30:00</date><tags><tag>#RedRanger</tag><tag>#Whitby42</tag><tag>#Mistakes</tag></tags><rst_text>Some witty epigram. Including &lt; &amp; &gt; characters.</rst_text></entry>
    </entries></blog>

"""

def build_blog(document: XML.ElementTree) -> Blog_X:
    xml_blog = document.getroot()
    blog = Blog_X(xml_blog.findtext("title"))
    for xml_post in xml_blog.findall("entries/entry"):
        optional_tag_iter = (
            t.text for t in xml_post.findall("tags/tag")
        )
        tags = list(
            filter(None, optional_tag_iter)
        )
        post = Post_X(
            date=datetime.datetime.strptime(
                xml_post.findtext("date"), "%Y-%m-%d %H:%M:%S"
            ),
            title=xml_post.findtext("title"),
            tags=tags,
            rst_text=xml_post.findtext("rst_text"),
        )
        blog.append(post)
    return blog

test_xml_in = """
    >>> tree = XML.ElementTree(travel5.xmlelt())
    >>> text = XML.tostring(tree.getroot())

    >>> document = XML.parse(io.StringIO(text.decode("utf-8")))
    >>> blog = build_blog(document)
    >>> rst_render(blog)  # doctest: +NORMALIZE_WHITESPACE
    Travel
    ======
    <BLANKLINE>
    <BLANKLINE>
        Hard Aground
        ------------
    <BLANKLINE>
        Some embarrassing revelation. Including ☹ and ⚓
    <BLANKLINE>
        :date: 2013-11-14 17:25:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #ICW
    <BLANKLINE>
    <BLANKLINE>
        Anchor Follies
        --------------
    <BLANKLINE>
        Some witty epigram. Including < & > characters.
    <BLANKLINE>
        :date: 2013-11-18 15:30:00
    <BLANKLINE>
        :tags: #RedRanger #Whitby42 #Mistakes
    <BLANKLINE>
    Tag Index
    =========
    <BLANKLINE>
    *   #RedRanger
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #Whitby42
    <BLANKLINE>
        -   `Hard Aground`_
        -   `Anchor Follies`_
    <BLANKLINE>
    *   #ICW
    <BLANKLINE>
        -   `Hard Aground`_
    <BLANKLINE>
    *   #Mistakes
    <BLANKLINE>
        -   `Anchor Follies`_
    <BLANKLINE>
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_11/__init__.py
================================================


================================================
FILE: Chapter_11/ch11_ex1.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 11. Example 1.
"""


# Shelve Basics
# ========================================

from typing import List, Dict, Any, Optional
from collections import defaultdict
import datetime
from pathlib import Path
from dataclasses import dataclass, asdict, field
import shelve


# Some Example Application Classes


@dataclass
class Post:
    date: datetime.datetime
    title: str
    rst_text: str
    tags: List[str]


@dataclass
class Blog:

    title: str
    entries: List[Post] = field(default_factory=list)
    underline: str = field(init=False)

    # Part of the persistence, not essential to the class.
    _id: str = field(default="", init=False, compare=False)

    def __post_init__(self) -> None:
        self.underline = "=" * len(self.title)

    def append(self, post: Post) -> None:
        self.entries.append(post)

    def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:
        tag_index: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
        for post in self.entries:
            for tag in post.tags:
                tag_index[tag].append(asdict(post))
        return tag_index

test_blog = """
    >>> b1 = Blog(title="Travel Blog")
    >>> b1
    Blog(title='Travel Blog', entries=[], underline='===========', _id='')

    >>> import shelve
    >>> from pathlib import Path
    >>> path = Path.cwd() / "data" / "ch11_blog1"
    >>> shelf = shelve.open(str(path), "n")
    >>> b1._id = 'Blog:1'
    >>> shelf[b1._id] = b1
    
    >>> shelf['Blog:1']
    Blog(title='Travel Blog', entries=[], underline='===========', _id='Blog:1')
    >>> shelf['Blog:1'].title 
    'Travel Blog'
    >>> shelf['Blog:1']._id 
    'Blog:1'
    >>> list(shelf.keys()) 
    ['Blog:1']
    >>> shelf.close() 
"""

test_query = """
    >>> path = Path.cwd() / "data" / "ch11_blog1"
    >>> shelf = shelve.open(str(path))
    >>> results = (shelf[k] 
    ...     for k in shelf.keys() 
    ...     if k.startswith('Blog:') and shelf[k].title == 'Travel Blog'
    ... )
    >>> list(results)
    [Blog(title='Travel Blog', entries=[], underline='===========', _id='Blog:1')]
"""

if __name__ == "__main__":
    # A Blog example
    b1 = Blog(title="Travel Blog")
    p1 = Post(
        date=datetime.datetime(2019, 1, 18),
        title="Some Post",
        rst_text="Details of the post",
        tags=["#sample", "#data"],
    )
    b1.append(p1)

    # Some Manual access
    import shelve

    shelf = shelve.open(str(Path.cwd() / "data" / "ch11_blog"))
    db_id = 0

    # Typical seqence for saving...
    db_id += 1
    b1._id = f"Blog:{db_id}"
    shelf[b1._id] = b1
    print(f"Create {shelf[b1._id]._id} {shelf[b1._id].title}")

    # Seaching through the shelf for a specific title...
    results = (
        shelf[k]
        for k in shelf.keys()
        if k.startswith("Blog:") and shelf[k].title == "Travel Blog"
    )
    for r0 in results:
        print(f"Retrieve {r0._id} {r0.title}")
        for p in r0.entries:
            print(f"  {p}")
        print(f"  {r0.by_tag()}")

    shelf.close()


# Some more manual access
if __name__ == "__main__":

    p2 = Post(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓︎""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )

    p3 = Post(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including ☺ and ☀︎︎""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )

    shelf = shelve.open(str(Path.cwd() / "data" / "ch11_blog"))

    # Retrieve the blog by id
    blog_id = 1
    key = f"Blog:{blog_id}"
    the_blog = shelf[key]

    # Update the blog
    the_blog.append(p2)
    the_blog.append(p3)

    # Persist the changes to the blog.
    shelf[key] = the_blog

    # What's in the database?
    print("Database has", list(shelf.keys()))

    shelf.close()


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_11/ch11_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 11. Example 2.
"""

from typing import List, Dict, Any, Optional, cast, Iterator, Union, TextIO
import datetime
from dataclasses import dataclass, field, asdict
from pathlib import Path

# Application Classes
# ====================

# Designed to be used separately.

@dataclass
class Post:
    date: datetime.datetime
    title: str
    rst_text: str
    tags: List[str]
    underline: str = field(init=False)
    tag_text: str = field(init=False)

    # Will be set as part of saving to the shelf.
    # Part of the persistence, not essential to the class.
    _id: str = field(default='', init=False, repr=False, compare=False)
    _blog_id: str = field(default='', init=False, repr=False, compare=False)

    def __post_init__(self) -> None:
        self.underline = "-" * len(self.title)
        self.tag_text = " ".join(self.tags)

@dataclass
class Blog:

    title: str
    underline: str = field(init=False)

    # Will be set as part of saving to the shelf.
    # Part of the persistence, not essential to the class.
    _id: str = field(default="", init=False, compare=False)

    def __post_init__(self) -> None:
        self.underline = "=" * len(self.title)

    def by_tag(self, access: 'Access') -> Dict[str, List[Dict[str, Any]]]:
        tag_index: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
        for post in access.post_iter(self):
            if post._blog_id == self._id:
                for tag in post.tags:
                    tag_index[tag].append(asdict(post))
        return tag_index


test_relational_1 = """
    >>> b1 = Blog(title="Travel Blog")
    
    >>> p2 = Post(date=datetime.datetime(2013,11,14,17,25), 
    ...        title="Hard Aground", 
    ...        rst_text="Some embarrassing revelation. Including ☹ and ⚓", 
    ...        tags=("#RedRanger", "#Whitby42", "#ICW"), 
    ...        ) 
 
    >>> p3 = Post(date=datetime.datetime(2013,11,18,15,30), 
    ...        title="Anchor Follies", 
    ...        rst_text="Some witty epigram. Including < & > characters.", 
    ...        tags=("#RedRanger", "#Whitby42", "#Mistakes"), 
    ...        ) 

    >>> import shelve
    >>> from pathlib import Path
    >>> path = Path.cwd() / "data" / "ch11_blog2"
    >>> shelf = shelve.open(str(path), 'n')
    
    >>> b1._id = 'Blog:1'
    >>> shelf[b1._id] = b1
    >>> list(shelf.keys())
    ['Blog:1']
    
    >>> owner = shelf['Blog:1'] 
    >>> owner
    Blog(title='Travel Blog', underline='===========', _id='Blog:1')
    
    >>> p2._parent = owner._id 
    >>> p2._id = p2._parent + ':Post:2' 
    >>> shelf[p2._id]= p2 
     
    >>> p3._parent = owner._id 
    >>> p3._id = p3._parent + ':Post:3' 
    >>> shelf[p3._id]= p3 
    
    >>> shelf.sync()

    >>> sorted(shelf.keys())
    ['Blog:1', 'Blog:1:Post:2', 'Blog:1:Post:3']
"""

# "Relational" Access Layer -- Separate Blogs from Posts
# ======================================================

# We'll use hierarchical keys Post:id and Post:id:Child:id
import shelve


class OperationError(Exception):
    pass


class Access:

    def __init__(self) -> None:
        self.database: shelve.Shelf = cast(shelve.Shelf, None)
        self.max: Dict[str, int] = {"Post": 0, "Blog": 0}

    def new(self, path: Path) -> None:
        self.database: shelve.Shelf = shelve.open(str(path), "n")
        self.max: Dict[str, int] = {"Post": 0, "Blog": 0}
        self.sync()

    def open(self, path: Path) -> None:
        self.database = shelve.open(str(path), "w")
        self.max = self.database["_DB:max"]

    def close(self) -> None:
        if self.database:
            self.database["_DB:max"] = self.max
            self.database.close()
        self.database = cast(shelve.Shelf, None)

    def sync(self) -> None:
        self.database["_DB:max"] = self.max
        self.database.sync()

    def create_blog(self, blog: Blog) -> Blog:
        self.max['Blog'] += 1
        key = f"Blog:{self.max['Blog']}"
        blog._id = key
        self.database[blog._id] = blog
        return blog

    def retrieve_blog(self, key: str) -> Blog:
        return self.database[key]

    def create_post(self, blog: Blog, post: Post) -> Post:
        self.max['Post'] += 1
        post_key = f"Post:{self.max['Post']}"
        post._id = post_key
        post._blog_id = blog._id
        self.database[post._id] = post
        return post

    def retrieve_post(self, key: str) -> Post:
        return self.database[key]

    def update_post(self, post: Post) -> Post:
        self.database[post._id] = post
        return post

    def delete_post(self, post: Post) -> None:
        del self.database[post._id]

    def __iter__(self) -> Iterator[Union[Blog, Post]]:
        for k in self.database:
            if k[0] == "_":
                # Skip the administrative objects
                continue
            yield self.database[k]

    def blog_iter(self) -> Iterator[Blog]:
        for k in self.database:
            if k.startswith('Blog:'):
                yield self.database[k]

    def post_iter(self, blog: Blog) -> Iterator[Post]:
        for k in self.database:
            if k.startswith('Post:'):
                if self.database[k]._blog_id == blog._id:
                    yield self.database[k]

    def post_title_iter(self, blog: Blog, title: str) -> Iterator[Post]:
        return (p for p in self.post_iter(blog) if p.title == title)

    def blog_title_iter(self, title: str) -> Iterator[Blog]:
        return (b for b in self.blog_iter() if b.title == title)


# Demonstration Script
from contextlib import closing

def database_script(access: Access) -> None:
    b1 = Blog(title="Travel Blog")
    p2 = Post(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓︎""",
        tags=["#RedRanger", "#Whitby42", "#ICW"],
    )

    p3 = Post(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including ☺ and ☀︎︎""",
        tags=["#RedRanger", "#Whitby42", "#Mistakes"],
    )
    access.create_blog(b1)
    for post in p2, p3:
        access.create_post(b1, post)

    b = access.retrieve_blog(b1._id)
    print(b._id, b)
    for p in sorted(access.post_iter(b), key=lambda p: p._id):
        print(p._id, p)

test_access = """
    >>> with closing(Access()) as access:
    ...     access.new(Path.cwd() / "data" / "ch11_blog")
    ...     database_script(access)
    Blog:1 Blog(title='Travel Blog', underline='===========', _id='Blog:1')
    Post:1 Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW'], underline='------------', tag_text='#RedRanger #Whitby42 #ICW')
    Post:2 Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including ☺ and ☀︎︎', tags=['#RedRanger', '#Whitby42', '#Mistakes'], underline='--------------', tag_text='#RedRanger #Whitby42 #Mistakes')
"""

# Another Application
# ==============================


import string
from collections import defaultdict
from contextlib import redirect_stdout
import sys

class Render:

    def __init__(self, access: Access) -> None:
        self.access = access

    def emit_all(self, destination: TextIO=sys.stdout) -> None:
        for blog in self.access.blog_iter():
            # Compute a filename for each blog.
            self.emit_blog(blog, destination)

    def emit_blog(self, blog: Blog, output: TextIO) -> None:
        with redirect_stdout(output):
            self.tag_index: Dict[str, List[str]] = defaultdict(list)
            print("{title}\n{underline}\n".format(**asdict(blog)))
            for post in self.access.post_iter(blog):
                self.emit_post(post)
                for tag in post.tags:
                    self.tag_index[tag].append(post._id)
            self.emit_index()

    def emit_post(self, post: Post) -> None:
        template = string.Template(
            """
        $title
        $underline

        $rst_text

        :date: $date

        :tags: $tag_text
        """
        )
        print(template.substitute(asdict(post)))

    def emit_index(self) -> None:
        print("Tag Index")
        print("=========")
        print()
        for tag in self.tag_index:
            print("*   {0}".format(tag))
            print()
            for b in self.tag_index[tag]:
                post = self.access.retrieve_post(b)
                print("    -   `{title}`_".format(**asdict(post)))
            print()


# Demo Script
import shelve
from contextlib import closing

if __name__ == "__main__":
    with closing(Access()) as access:
        access.open(Path.cwd() / "data" / "ch11_blog")
        renderer = Render(access)
        renderer.emit_all()


# Better Access Layer
# ======================================

# Maintain a indexes associated with the Blog


class Access2(Access):

    def create_post(self, blog: Blog, post: Post) -> Post:
        super().create_post(blog, post)
        # Update the index; append doesn't work.
        blog_index = f"_Index:{blog._id}"
        self.database.setdefault(blog_index, [])
        self.database[blog_index] = self.database[blog_index] + [post._id]
        return post

    def delete_post(self, post: Post) -> None:
        super().delete_post(post)
        # Update the index
        blog_index = f"_Index:{post._blog_id}"
        index_list = self.database[post._blog_id]
        index_list.remove(post._id)
        self.database[post._blog_id] = index_list

    def post_iter(self, blog: Blog) -> Iterator[Post]:
        blog_index = f"_Index:{blog._id}"
        for k in self.database[blog_index]:
            yield self.database[k]


test_access_2 = """
    >>> with closing(Access2()) as access:
    ...     access.new(Path.cwd() / "data" / "ch11_blog2")
    ...     database_script(access)
    Blog:1 Blog(title='Travel Blog', underline='===========', _id='Blog:1')
    Post:1 Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW'], underline='------------', tag_text='#RedRanger #Whitby42 #ICW')
    Post:2 Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including ☺ and ☀︎︎', tags=['#RedRanger', '#Whitby42', '#Mistakes'], underline='--------------', tag_text='#RedRanger #Whitby42 #Mistakes')

    >>> with closing(Access2()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog2")
    ...     print(sorted(access.database.keys()))
    ...     print(access.database['_Index:Blog:1'])
    ['Blog:1', 'Post:1', 'Post:2', '_DB:max', '_Index:Blog:1']
    ['Post:1', 'Post:2']
    
    
    >>> with closing(Access2()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog2")
    ...     renderer = Render(access)
    ...     renderer.emit_all()
"""

# Minor Index
# ==========================

# Another version of Access with slightly different blog add and search.
# This a tiny help, because the iteration over the cached blog keys
# is slightly faster.

class Access3(Access2):

    def new(self, path: Path) -> None:
        super().new(path)
        self.database["_Index:Blog"] = list()

    def create_blog(self, blog: Blog) -> Blog:
        super().create_blog(blog)
        self.database["_Index:Blog"] += [blog._id]
        return blog

    def blog_iter(self) -> Iterator[Blog]:
        return (self.database[k] for k in self.database["_Index:Blog"])


test_access_3 = """
    >>> with closing(Access3()) as access:
    ...     access.new(Path.cwd() / "data" / "ch11_blog3")
    ...     database_script(access)
    Blog:1 Blog(title='Travel Blog', underline='===========', _id='Blog:1')
    Post:1 Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW'], underline='------------', tag_text='#RedRanger #Whitby42 #ICW')
    Post:2 Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including ☺ and ☀︎︎', tags=['#RedRanger', '#Whitby42', '#Mistakes'], underline='--------------', tag_text='#RedRanger #Whitby42 #Mistakes')

    >>> with closing(Access3()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog3")
    ...     print(sorted(access.database.keys()))
    ...     print(access.database['_Index:Blog:1'])
    ['Blog:1', 'Post:1', 'Post:2', '_DB:max', '_Index:Blog', '_Index:Blog:1']
    ['Post:1', 'Post:2']


    >>> with closing(Access3()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog3")
    ...     renderer = Render(access)
    ...     renderer.emit_all()
"""


# Additional Indices
# ================================

# A class with multiple indices.
# Is this really worth the extra complexity?
class Access4(Access3):

    def new(self, path: Path) -> None:
        super().new(path)
        self.database["_Index:Blog_Title"] = dict()

    def create_blog(self, blog):
        super().create_blog(blog)
        blog_title_dict = self.database["_Index:Blog_Title"]
        blog_title_dict.setdefault(blog.title, [])
        blog_title_dict[blog.title].append(blog._id)
        self.database["_Index:Blog_Title"] = blog_title_dict
        return blog

    def update_blog(self, blog: Blog) -> Blog:
        """Replace this Blog; update index."""
        self.database[blog._id] = blog
        blog_title = self.database["_Index:Blog_Title"]
        # Remove key from index in old spot.
        empties = []
        for k in blog_title:
            if blog._id in blog_title[k]:
                blog_title[k].remove(blog._id)
                if len(blog_title[k]) == 0:
                    empties.append(k)
        # Cleanup zero-length lists from defaultdict.
        for k in empties:
            del blog_title[k]
        # Put key into index in new spot.
        blog_title[blog.title].append(blog._id)
        self.database["_Index:Blog_Title"] = blog_title
        return blog

    def blog_iter(self) -> Iterator[Blog]:
        return (self.database[k] for k in self.database["_Index:Blog"])

    def blog_title_iter(self, title: str) -> Iterator[Blog]:
        blog_title = self.database["_Index:Blog_Title"]
        return (self.database[k] for k in blog_title[title])


test_access_4 = """
    >>> with closing(Access4()) as access:
    ...     access.new(Path.cwd() / "data" / "ch11_blog4")
    ...     database_script(access)
    Blog:1 Blog(title='Travel Blog', underline='===========', _id='Blog:1')
    Post:1 Post(date=datetime.datetime(2013, 11, 14, 17, 25), title='Hard Aground', rst_text='Some embarrassing revelation. Including ☹ and ⚓︎', tags=['#RedRanger', '#Whitby42', '#ICW'], underline='------------', tag_text='#RedRanger #Whitby42 #ICW')
    Post:2 Post(date=datetime.datetime(2013, 11, 18, 15, 30), title='Anchor Follies', rst_text='Some witty epigram. Including ☺ and ☀︎︎', tags=['#RedRanger', '#Whitby42', '#Mistakes'], underline='--------------', tag_text='#RedRanger #Whitby42 #Mistakes')

    >>> with closing(Access4()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog4")
    ...     print(sorted(access.database.keys()))
    ...     print(access.database['_Index:Blog:1'])
    ['Blog:1', 'Post:1', 'Post:2', '_DB:max', '_Index:Blog', '_Index:Blog:1', '_Index:Blog_Title']
    ['Post:1', 'Post:2']


    >>> with closing(Access4()) as access:
    ...     access.open(Path.cwd() / "data" / "ch11_blog4")
    ...     renderer = Render(access)
    ...     renderer.emit_all()
"""


# Timing Comparison
# ================================

# Larger Database Required
import time
import io


def create(access, blogs=100, posts_per_blog=100) -> None:
    for b_n in range(blogs):
        b = Blog("Blog {0}".format(b_n))
        access.create_blog(b)
        for p_n in range(posts_per_blog):
            p = Post(
                date=datetime.datetime.now(),
                title="Blog {0}; Post {1}".format(b_n, p_n),
                rst_text="Blog {0}; Post {1}\nText\n".format(b_n, p_n),
                tags=list("#tag{0}".format(p_n + i) for i in range(3)),
            )
            access.create_post(b, p)


def performance(cycles=3):
    import random

    result: Dict[str, float] = defaultdict(int)
    base_path = Path.cwd() / "data"

    for filename, class_ in (
        (base_path / "ch11_blog_t", Access),
        (base_path / "ch11_blog_t2", Access2),
        (base_path / "ch11_blog_t3", Access3),
        (base_path / "ch11_blog_t4", Access4),
    ):

        buffer = io.StringIO()
        start = time.perf_counter()
        for _ in range(cycles):
            with closing(class_()) as access:
                access.new(filename)
                create(access, blogs=100, posts_per_blog=100)
            with closing(class_()) as access:
                access.open(filename)
                renderer = Render(access)
                renderer.emit_all(buffer)
            with closing(class_()) as access:
                access.open(filename)
                renderer = Render(access)
                titles = []
                for i in range(10):
                    choice = random.randint(1, 100)
                    blog_by_id = access.retrieve_blog(f"Blog:{choice}")
                    renderer.emit_blog(blog_by_id, buffer)
                    titles.append(blog_by_id.title)
            with closing(class_()) as access:
                access.open(filename)
                renderer = Render(access)
                for t in titles:
                    blogs = access.blog_title_iter(t)
                    for b in blogs:
                        renderer.emit_blog(b, buffer)
        finish = time.perf_counter()
        result[class_.__name__] = finish - start

    print("Time to create and render 10,000 posts")
    for r in sorted(result):
        print(
            f"Access Layer {r}: {result[r]/cycles:.1f} seconds "
        )

    for path in base_path.glob("ch11_blog_t*.*"):
        path.unlink()


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)
    # performance()   # Takes 45 seconds

"""
Time to create and render 10,000 posts
Access Layer Access: 33.5 seconds 
Access Layer Access2: 4.0 seconds 
Access Layer Access3: 3.9 seconds 
Access Layer Access4: 4.0 seconds 
"""

================================================
FILE: Chapter_12/__init__.py
================================================


================================================
FILE: Chapter_12/ch12_ex1.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 12. Example 1.
"""

from typing import Dict, List, Tuple

# One issue here is that the microblog has no processing.
# The classes tend to be rather anemic.

# The upside is that it has all of the relevant relationships
# So it shows SQL key handling nicely.

# SQL Basics
# ========================================

# Some Example Table Declarations for a simple microblog.
sql_cleanup = """
DROP TABLE IF EXISTS blog;
DROP TABLE IF EXISTS post;
DROP TABLE IF EXISTS tag;
DROP TABLE IF EXISTS assoc_post_tag;
"""

sql_ddl = """
CREATE TABLE blog(
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
    TITLE TEXT 
);
CREATE TABLE post(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    date TIMESTAMP,
    title TEXT,
    rst_text TEXT,
    blog_id INTEGER REFERENCES blog(id)
);
CREATE TABLE tag(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    phrase TEXT UNIQUE ON CONFLICT FAIL
);
CREATE TABLE assoc_post_tag(
    post_id INTEGER REFERENCES post(id),
    tag_id INTEGER REFERENCES tag(id)
);
"""

import sqlite3
from pathlib import Path
from contextlib import closing

database = sqlite3.connect(Path.cwd() / "data" / "ch12_blog.db")  # type: ignore
# Note that sqlite3 really does use a Path. The declaration doesn't include it.
# We have two choices.
# 1. Use a ``# typing: ignore`` comment
# 2. use ``str(Path.cwd()/"data"/"ch12_blog.db")``

# reveal_type(sqlite3.connect)

database.executescript(sql_cleanup)

with closing(database.cursor()) as cursor:
    for stmt in (stmt.rstrip() for stmt in sql_ddl.split(";")):
        print(stmt)
        cursor.execute(stmt)
        print(cursor)
database.commit()
database.close()

# ACID
# ===============

database = sqlite3.connect(
    Path.cwd() / "data" / "ch12_blog.db", isolation_level="DEFERRED"
)  # type: ignore
try:
    with closing(database.cursor()) as cursor:
        cursor.execute("BEGIN")
        # cursor.execute("some statement")
        # cursor.execute("another statement")
    database.commit()
except Exception as e:
    database.rollback()

# Simple SQL
# ======================

# Import
import datetime

# Connection
database = sqlite3.connect(Path.cwd() / "data" / "ch12_blog.db")  # type: ignore

# Useful query to figuring out what PK was automatically assigned.
get_last_id = """
SELECT last_insert_rowid()
"""

with closing(database.cursor()) as cursor:
    cursor.execute("BEGIN")

    # Build BLOG
    create_blog = """
        INSERT INTO blog(title) VALUES(?)
    """
    cursor.execute(create_blog, ("Travel Blog",))
    row = cursor.execute(get_last_id).fetchone()
    blog_id = row[0]

    # Build POST
    create_post = """
        INSERT INTO post(date, title, rst_text, blog_id) VALUES(?, ?, ?, ?)
    """
    cursor.execute(
        create_post,
        (
            datetime.datetime(2013, 11, 14, 17, 25),
            "Hard Aground",
            """Some embarrassing revelation. Including ☹ and ⚓︎""",
            blog_id,
        ),
    )
    row = cursor.execute(get_last_id).fetchone()
    post_id = row[0]

    # Build TAGs for a Post
    create_tag = """
        INSERT INTO tag(phrase) VALUES(?)
    """
    retrieve_tag = """
        SELECT id, phrase FROM tag WHERE phrase = ?
    """
    create_tag_post_association = """
        INSERT INTO assoc_post_tag(post_id, tag_id) VALUES (?, ?)
    """
    for tag in ("#RedRanger", "#Whitby42", "#ICW"):
        row = cursor.execute(retrieve_tag, (tag,)).fetchone()
        if row:
            tag_id = row[0]
        else:
            cursor.execute(create_tag, (tag,))
            row = cursor.execute(get_last_id).fetchone()
            tag_id = row[0]
        cursor.execute(create_tag_post_association, (post_id, tag_id))

database.commit()

update_blog = """
    UPDATE blog SET title=:new_title WHERE title=:old_title
"""
with closing(database.cursor()) as cursor:
    # Sample Update
    cursor.execute("BEGIN")
    cursor.execute(
        update_blog,
        dict(
            new_title="2013-2014 Travel",
            old_title="Travel Blog")
    )
database.commit()

# Sample Delete
delete_post_tag_by_blog_title = """
    DELETE FROM assoc_post_tag
    WHERE post_id IN (
        SELECT DISTINCT post_id
        FROM blog JOIN post ON blog.id = post.blog_id
        WHERE blog.title=:old_title)
"""
delete_post_by_blog_title = """
    DELETE FROM post WHERE blog_id IN (
        SELECT id FROM blog WHERE title=:old_title)
"""
delete_blog_by_title = """
    DELETE FROM blog WHERE title=:old_title
"""
try:
    with closing(database.cursor()) as cursor:
        title = dict(old_title="2013-2014 Travel")
        cursor.execute("BEGIN")
        cursor.execute(delete_post_tag_by_blog_title, title)
        cursor.execute(delete_post_by_blog_title, title)
        cursor.execute(delete_blog_by_title, title)
        print("Post Delete, Pre Commit; should be no '2013-2014 Travel'")
        cursor.execute("SELECT * FROM blog")
        for row in cursor.fetchall():
            print(row)
        cursor.execute("SELECT * FROM post")
        for row in cursor.fetchall():
            print(row)
        cursor.execute("SELECT * FROM assoc_post_tag")
        for row in cursor.fetchall():
            print(row)
        raise Exception("Demonstrating an Error")
    print("Should not get here to commit.")
    database.commit()
except Exception as ex:
    print(f"Rollback due to {ex!r}")
    database.rollback()

# Bulk examination of database to show simple queries
with closing(database.cursor()) as cursor:
    print("Dumping whole database.")
    for row in cursor.execute("SELECT * FROM blog"):
        print("BLOG", row)
    for row in cursor.execute("SELECT * FROM post"):
        print("POST", row)
    for row in cursor.execute("SELECT * FROM tag"):
        print("TAG", row)
    for row in cursor.execute(
        """
        SELECT assoc_post_tag.* 
        FROM post 
            JOIN assoc_post_tag ON post.id=assoc_post_tag.post_id 
            JOIN tag ON tag.id=assoc_post_tag.tag_id
        """
    ):
        print("ASSOC_POST_TAG", row)

# Naked SQL Query
# ==========================

print("Dump a single blog by title.")

# Three-step nested queries
query_blog_by_title = """
SELECT * FROM blog WHERE title=?
"""
query_post_by_blog_id = """
SELECT * FROM post WHERE blog_id=?
"""
query_tag_by_post_id = """
SELECT tag.*
FROM tag JOIN assoc_post_tag ON tag.id = assoc_post_tag.tag_id
WHERE assoc_post_tag.post_id=?
"""
with closing(database.cursor()) as blog_cursor:
    blog_cursor.execute(query_blog_by_title, ("2013-2014 Travel",))
    for blog in blog_cursor.fetchall():
        print("Blog", blog)
        with closing(database.cursor()) as post_cursor:
            post_cursor.execute(query_post_by_blog_id, (blog[0],))
            for post in post_cursor:
                print("Post", post)
                with closing(database.cursor()) as tag_cursor:
                    tag_cursor.execute(query_tag_by_post_id, (post[0],))
                    for tag in tag_cursor.fetchall():
                        print("Tag", tag)

# Tag index
from collections import defaultdict

query_by_tag = """
    SELECT tag.phrase, post.title, post.id
    FROM tag JOIN assoc_post_tag ON tag.id = assoc_post_tag.tag_id
    JOIN post ON post.id = assoc_post_tag.post_id
    JOIN blog ON post.blog_id = blog.id
    WHERE blog.title=?
"""
tag_index: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
with closing(database.cursor()) as cursor:
    cursor.execute(query_by_tag, ("2013-2014 Travel",))
    for tag, post_title, post_id in cursor.fetchall():
        tag_index[tag].append((post_title, post_id))
    print(tag_index)

database.close()

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_12/ch12_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 12. Example 2.
"""

# BLOB Mapping
# =========================

# Adding Decimal data to a SQLite database.
import sqlite3
import decimal


def adapt_currency(value):
    return str(value)


sqlite3.register_adapter(decimal.Decimal, adapt_currency)


def convert_currency(bytes):
    return decimal.Decimal(bytes.decode())


sqlite3.register_converter("DECIMAL", convert_currency)

# When we define a table, we must use the type "decimal"
# to get two-digit decimal values.

decimal_cleanup = """
DROP TABLE IF EXISTS budget
"""

decimal_ddl = """
CREATE TABLE budget(
    year INTEGER,
    month INTEGER,
    category TEXT,
    amount DECIMAL
)
"""
insert_budget = """
INSERT INTO budget(year, month, category, amount) VALUES(:year, :month, :category, :amount)
"""
query_budget = """
SELECT * FROM budget
"""

test_decimal = """
>>> from pathlib import Path
>>> database = sqlite3.connect(
...    Path.cwd() / "data" / "ch12_blog.db", 
...    detect_types=sqlite3.PARSE_DECLTYPES  # Required to include additional types
... )  # type: ignore

>>> _ = database.execute(decimal_cleanup)
>>> _ = database.execute(decimal_ddl)

>>> _ = database.execute(
...    insert_budget,
...    dict(year=2013, month=1, category="fuel", amount=decimal.Decimal("256.78")),
... )
>>> _ = database.execute(
...    insert_budget,
...    dict(year=2013, month=2, category="fuel", amount=decimal.Decimal("287.65")),
... )

>>> for row in database.execute(query_budget):
...    print(row)
(2013, 1, 'fuel', Decimal('256.78'))
(2013, 2, 'fuel', Decimal('287.65'))
>>> database.close()
"""

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_12/ch12_ex3.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 12. Example 3.
"""

# Manual ORM
# =========================

from pathlib import Path
from dataclasses import dataclass, asdict, field
from typing import List, Dict, Any, DefaultDict, Optional, Iterator, cast
import sqlite3
import datetime
from contextlib import closing
from collections import defaultdict
from weakref import ref


@dataclass
class Blog:

    title: str
    underline: str = field(init=False)

    # Part of the persistence, not essential to the class.
    _id: str = field(default="", init=False, compare=False)
    _access: Optional[ref] = field(init=False, repr=False, default=None, compare=False)

    def __post_init__(self) -> None:
        self.underline = "=" * len(self.title)

    @property
    def entries(self) -> List['Post']:
        if self._access and self._access():
            posts = cast('Access', self._access()).post_iter(self)
            return list(posts)
        raise RuntimeError("Can't work with Blog: no associated Access instance")

    def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:
        if self._access and self._access():
            return cast('Access', self._access()).post_by_tag(self)
        raise RuntimeError("Can't work with Blog: no associated Access instance")

@dataclass
class Post:

    date: datetime.datetime
    title: str
    rst_text: str
    tags: List[str] = field(default_factory=list)
    _id: str = field(default="", init=False, compare=False)

    def append(self, tag):
        self.tags.append(tag)


# An access layer to map back and forth between Python objects and SQL rows.
class Access:
    get_last_id = """
        SELECT last_insert_rowid()
    """

    def open(self, path: Path) -> None:
        self.database = sqlite3.connect(path)
        self.database.row_factory = sqlite3.Row

    def get_blog(self, id: str) -> Blog:
        query_blog = """
            SELECT * FROM blog WHERE id=?
        """
        row = self.database.execute(query_blog, (id,)).fetchone()
        blog = Blog(title=row["TITLE"])
        blog._id = row["ID"]
        blog._access = ref(self)
        return blog

    def add_blog(self, blog: Blog) -> Blog:
        insert_blog = """
            INSERT INTO blog(title) VALUES(:title)
        """
        self.database.execute(insert_blog, dict(title=blog.title))
        row = self.database.execute(self.get_last_id).fetchone()
        blog._id = str(row[0])
        blog._access = ref(self)
        return blog

    def get_post(self, id: str) -> Post:
        query_post = """
            SELECT * FROM post WHERE id=?
        """
        row = self.database.execute(query_post, (id,)).fetchone()
        post = Post(
            title=row["TITLE"], date=row["DATE"], rst_text=row["RST_TEXT"]
        )
        post._id = row["ID"]
        # Get tag text, too
        query_tags = """
            SELECT tag.*
            FROM tag JOIN assoc_post_tag ON tag.id = assoc_post_tag.tag_id
            WHERE assoc_post_tag.post_id=?
        """
        results = self.database.execute(query_tags, (id,))
        for tag_id, phrase in results:
            post.append(phrase)
        return post

    def add_post(self, blog: Blog, post: Post) -> Post:
        insert_post = """
            INSERT INTO post(title, date, rst_text, blog_id) VALUES(:title, :date, :rst_text, :blog_id)
        """
        query_tag = """
            SELECT * FROM tag WHERE phrase=?
        """
        insert_tag = """
            INSERT INTO tag(phrase) VALUES(?)
        """
        insert_association = """
            INSERT INTO assoc_post_tag(post_id, tag_id) VALUES(:post_id, :tag_id)
        """
        try:
            with closing(self.database.cursor()) as cursor:
                cursor.execute(
                    insert_post,
                    dict(
                        title=post.title,
                        date=post.date,
                        rst_text=post.rst_text,
                        blog_id=blog._id,
                    ),
                )
                row = cursor.execute(self.get_last_id).fetchone()
                post._id = str(row[0])
                for tag in post.tags:
                    tag_row = cursor.execute(query_tag, (tag,)).fetchone()
                    if tag_row is not None:
                        tag_id = tag_row["ID"]
                    else:
                        cursor.execute(insert_tag, (tag,))
                        row = cursor.execute(self.get_last_id).fetchone()
                        tag_id = str(row[0])
                    cursor.execute(
                        insert_association,
                        dict(tag_id=tag_id, post_id=post._id)
                    )
            self.database.commit()
        except Exception as ex:
            self.database.rollback()
            raise
        return post

    def blog_iter(self) -> Iterator[Blog]:
        query = """
            SELECT * FROM blog
        """
        results = self.database.execute(query)
        for row in results:
            blog = Blog(title=row["TITLE"])
            blog._id = row["ID"]
            blog._access = ref(self)
            yield blog

    def post_iter(self, blog: Blog) -> Iterator[Post]:
        query = """
            SELECT id FROM post WHERE blog_id=?
        """
        results = self.database.execute(query, (blog._id,))
        for row in results:
            yield self.get_post(row["ID"])

    def post_by_tag(self, blog: Blog) -> Dict[str, List[Dict[str, Any]]]:
        """All posts of a blog, organized by tag, represented as dictionaries."""
        query_by_tag = """ 
            SELECT tag.phrase, post.id
            FROM tag JOIN assoc_post_tag ON tag.id = assoc_post_tag.tag_id 
            JOIN post ON post.id = assoc_post_tag.post_id 
            JOIN blog ON post.blog_id = blog.id 
            WHERE blog.title=? 
        """
        results = self.database.execute(query_by_tag, (blog.title,))
        tags: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
        for phrase, post_id in results.fetchall():
            tags[phrase].append(asdict(self.get_post(post_id)))
        return tags


if __name__ == "__main__":
    database_access = Access()
    database_access.open(Path.cwd() / "data" / "ch12_blog.db")
    b = Blog(title="2012 Travel")
    database_access.add_blog(b)
    print(b._id)
    p = Post(
        title="Some History",
        date=datetime.datetime(2012, 9, 16, 10, 00),
        rst_text="Some historyical notes.",
        tags=["#History", "#RedRanger"],
    )
    database_access.add_post(b, p)

    d = b.by_tag()
    print(d)

    for b in database_access.blog_iter():
        # print(f"b = {iter(b)}")
        print(asdict(b))
        for p in database_access.post_iter(b):
            print(asdict(p))

__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_12/ch12_ex4.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 12. Example 4.

..  important::

    SQLAlchemy doesn't include any stubs or type hints.

    There's no point in running mypy on this module. You'll see the following
    errors (plus a few others)::

        Chapter_12/ch12_ex4.py:18: error: No library stub file for module 'sqlalchemy.ext.declarative'
        Chapter_12/ch12_ex4.py:18: note: (Stub files are from https://github.com/python/typeshed)
        Chapter_12/ch12_ex4.py:23: error: No library stub file for module 'sqlalchemy'
        Chapter_12/ch12_ex4.py:44: error: No library stub file for module 'sqlalchemy.orm'
        Chapter_12/ch12_ex4.py:117: error: No library stub file for module 'sqlalchemy.exc'

"""

import datetime

# SQLAlchemy Mapping
# ==============================

# Some Classes that reflect our SQL data.
from sqlalchemy.ext.declarative import declarative_base

# Section 3.2.5 lists the column types
from sqlalchemy import Column, Table
from sqlalchemy import (
    BigInteger,
    Boolean,
    Date,
    DateTime,
    Enum,
    Float,
    Integer,
    Interval,
    LargeBinary,
    Numeric,
    PickleType,
    SmallInteger,
    String,
    Text,
    Time,
    Unicode,
    UnicodeText,
    ForeignKey,
)
from sqlalchemy.orm import relationship, backref

# There are standard types and vendor types, also.
# We'll stick with generic types.

# The metaclass
Base = declarative_base()

# The application class/table declarations
class Blog(Base):
    __tablename__ = "BLOG"
    id = Column(Integer, primary_key=True)
    title = Column(String)

    def as_dict(self):
        return dict(
            title=self.title,
            underline="=" * len(self.title),
            entries=[e.as_dict() for e in self.entries],
        )


assoc_post_tag = Table(
    "ASSOC_POST_TAG",
    Base.metadata,
    Column("POST_ID", Integer, ForeignKey("POST.id")),
    Column("TAG_ID", Integer, ForeignKey("TAG.id")),
)


class Post(Base):
    __tablename__ = "POST"
    id = Column(Integer, primary_key=True)
    title = Column(String)
    date = Column(DateTime)
    rst_text = Column(UnicodeText)
    blog_id = Column(Integer, ForeignKey("BLOG.id"))
    blog = relationship("Blog", backref="entries")
    tags = relationship("Tag", secondary=assoc_post_tag, backref="posts")

    def as_dict(self):
        return dict(
            title=self.title,
            underline="-" * len(self.title),
            date=self.date,
            rst_text=self.rst_text,
            tags=[t.phrase for t in self.tags],
        )


class Tag(Base):
    __tablename__ = "TAG"
    id = Column(Integer, primary_key=True)
    phrase = Column(String, unique=True)


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)

    # Building a schema
    from sqlalchemy import create_engine

    engine = create_engine("sqlite:///./data/ch12_blog2.db", echo=True)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

    # Loading some data
    import sqlalchemy.exc
    from sqlalchemy.orm import sessionmaker

    Session = sessionmaker(bind=engine)

    session = Session()

    blog = Blog(title="Travel 2013")
    session.add(blog)

    tags = []
    for phrase in "#RedRanger", "#Whitby42", "#ICW":
        try:
            tag = session.query(Tag).filter(Tag.phrase == phrase).one()
        except sqlalchemy.orm.exc.NoResultFound:
            tag = Tag(phrase=phrase)
            session.add(tag)
        tags.append(tag)

    p2 = Post(
        date=datetime.datetime(2013, 11, 14, 17, 25),
        title="Hard Aground",
        rst_text="""Some embarrassing revelation. Including ☹ and ⚓︎""",
        blog=blog,
        tags=tags,
    )
    session.add(p2)

    tags = []
    for phrase in "#RedRanger", "#Whitby42", "#Mistakes":
        try:
            tag = session.query(Tag).filter(Tag.phrase == phrase).one()
        except sqlalchemy.orm.exc.NoResultFound:
            tag = Tag(phrase=phrase)
            session.add(tag)
        tags.append(tag)

    p3 = Post(
        date=datetime.datetime(2013, 11, 18, 15, 30),
        title="Anchor Follies",
        rst_text="""Some witty epigram. Including ☺ and ☀︎︎""",
        blog=blog,
        tags=tags,
    )
    session.add(p3)
    blog.posts = [p2, p3]

    session.commit()

    session = Session()

    for blog in session.query(Blog):
        print("{title}\n{underline}\n".format(**blog.as_dict()))
        for p in blog.entries:
            print(p.as_dict())

    session2 = Session()
    results = (
        session2.query(Post).join(assoc_post_tag).join(Tag).filter(
            Tag.phrase == "#Whitby42"
        )
    )
    for post in results:
        print(post.blog.title, post.date, post.title, [t.phrase for t in post.tags])


================================================
FILE: Chapter_13/__init__.py
================================================


================================================
FILE: Chapter_13/cards_openapi.json
================================================
{
  "openapi": "3.0.0",
  "info": {
    "description": "Deals simple hands of cards",
    "version": "2019.02",
    "title": "Chapter 13. Example 2"
  },
  "components": {
    "schemas": {
      "cards": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "rank": {
              "type": "number"
            },
            "suit": {
              "type": "string"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/cards/{n}": {
      "get": {
        "summary": "Get a hand of cards",
        "parameters": [
          {
            "in": "path",
            "name": "n",
            "description": "Number of Cards",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hand of cards",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "cards": {
                      "$ref": "#/components/schemas/cards"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          }
        }
      }
    },
    "/hands/{h}/cards/{c}": {
      "get": {
        "summary": "Get several hands of cards",
        "parameters": [
          {
            "in": "path",
            "name": "h",
            "description": "Number of Hands",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "in": "path",
            "name": "c",
            "description": "Number of Cards",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of hands of cards",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "hands": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/cards"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input"
          }
        }
      }
    }
  }
}

================================================
FILE: Chapter_13/ch13_e1_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 2.
"""


# REST basics
# ========================================

# Stateless. Roulette.  Base class definitions.

from typing import Optional, Iterable, TYPE_CHECKING

if TYPE_CHECKING:
    from wsgiref.types import WSGIApplication, WSGIEnvironment, StartResponse

import random
from Chapter_13.ch13_ex1 import (
    Wheel,
    Zero,
    DoubleZero,
    American,
    European,
    Response,
    json_get,
)


import sys
import wsgiref.util
import json


# REST Revised: Callable WSGI Applications
# =========================================


class Wheel2(Wheel):

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        winner = self.spin()
        status = "200 OK"
        headers = [("Content-type", "application/json; charset=utf-8")]
        start_response(status, headers)
        return [json.dumps(winner).encode("UTF-8")]


class American2(DoubleZero, Wheel2):
    pass


class European2(Zero, Wheel2):
    pass


test_wheel = """
    >>> am = American2(seed=2)
    >>> def mock_start(status, headers):
    ...     print(status, headers)
    >>> am({}, mock_start)
    200 OK [('Content-type', 'application/json; charset=utf-8')]
    [b'{"4": [35, 1], "Black": [1, 1], "Lo": [1, 1], "Even": [1, 1]}']
"""


# A WSGI wrapper application.
import sys


class Wheel3:

    def __init__(self, seed: Optional[int] = None) -> None:
        self.am = American2(seed)
        self.eu = European2(seed)

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        request = wsgiref.util.shift_path_info(environ)  # 1. Parse.
        print("Wheel3", request, file=sys.stderr)  # 2. Logging.
        if request and request.lower().startswith("eu"):  # 3. Evaluate.
            response = self.eu(environ, start_response)
        else:
            response = self.am(environ, start_response)
        return response  # 4. Respond.


test_wheel3 = """
    >>> wheel = Wheel3(seed=2)
    >>> def mock_start(status, headers):
    ...     print(status, headers)
    >>> wheel({"PATH_INFO": "/am"}, mock_start)
    200 OK [('Content-type', 'application/json; charset=utf-8')]
    [b'{"4": [35, 1], "Black": [1, 1], "Lo": [1, 1], "Even": [1, 1]}']
"""

# Revised Server
def roulette_server_3(count: int = 1) -> None:
    from wsgiref.simple_server import make_server

    httpd = make_server("localhost", 8080, Wheel3(2))
    if count is None:
        httpd.serve_forever()
    else:
        for c in range(count):
            httpd.handle_request()


# Wheel3 Demo
# ---------------

# When run as the main script, start a server and interact with it.
def server_3() -> None:

    import concurrent.futures
    import time

    with concurrent.futures.ProcessPoolExecutor() as executor:
        srvr = executor.submit(roulette_server_3, 2)
        time.sleep(0.1)  # Wait for the server to start
        r1 = json_get("/am")
        r2 = json_get("/eu")
        assert (
            str(r1)
            == "200: {'4': [35, 1], 'Black': [1, 1], 'Lo': [1, 1], 'Even': [1, 1]}"
        )
        assert (
            str(r2)
            == "200: {'4': [35, 1], 'Black': [1, 1], 'Lo': [1, 1], 'Even': [1, 1]}"
        )


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)
    server_3()


================================================
FILE: Chapter_13/ch13_e1_ex3.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 3.
"""


# REST basics
# ========================================

# Stateless. Roulette.  Base class definitions.

from typing import Dict, Tuple, List, Any, Optional, Iterable, TYPE_CHECKING

if TYPE_CHECKING:
    from wsgiref.types import WSGIApplication, WSGIEnvironment, StartResponse

import random
from Chapter_13.ch13_ex1 import (
    Wheel,
    Zero,
    DoubleZero,
    American,
    European,
    Response,
    json_get,
)


import sys
import wsgiref.util
import json


# REST Revised: Callable WSGI Applications
# =========================================


class Wheel2(Wheel):

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        winner = self.spin()
        status = "200 OK"
        headers = [("Content-type", "application/json; charset=utf-8")]
        start_response(status, headers)
        return [json.dumps(winner).encode("UTF-8")]


class American2(DoubleZero, Wheel2):
    pass


class European2(Zero, Wheel2):
    pass


# A WSGI wrapper application.
import sys


class Wheel3:

    def __init__(self) -> None:
        self.am = American2()
        self.eu = European2()

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        request = wsgiref.util.shift_path_info(environ)  # 1. Parse.
        print("Wheel3", request, file=sys.stderr)  # 2. Logging.
        if request and request.lower().startswith("eu"):  # 3. Evaluate.
            response = self.eu(environ, start_response)
        else:
            response = self.am(environ, start_response)
        return response  # 4. Respond.


# REST with sessions and state
# ========================================

# Player and Bet for Roulette.

# CRUD design issues.
# Player:
# - GET to see stake and rounds played.
# Bet:
# - POST to create a series of bets or decline to bet.
# - GET to see bets.
# Wheel:
# - GET to get spin and payout.

# Stateful object
from collections import defaultdict


class Table:

    def __init__(self, stake: int = 100) -> None:
        self.bets: Dict[str, int] = defaultdict(int)
        self.stake = stake

    def place_bet(self, name: str, amount: int) -> None:
        self.bets[name] += amount

    def clear_bets(self, name: str) -> None:
        self.bets: Dict[str, int] = defaultdict(int)

    def resolve(self, spin: Dict[str, Tuple[int, int]]) -> List[Tuple[str, int, str]]:
        """spin is a dict with bet:(x:y)."""
        details = []
        while self.bets:
            bet, amount = self.bets.popitem()
            if bet in spin:
                x, y = spin[bet]
                self.stake += int(amount * x / y)
                details.append((bet, amount, "win"))
            else:
                self.stake -= amount
                details.append((bet, amount, "lose"))
        return details


# WSGI Applications
class WSGI:

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        raise NotImplementedError


class RESTException(Exception):
    pass


class Roulette(WSGI):

    def __init__(self, wheel: Wheel) -> None:
        self.table = Table(100)
        self.rounds = 0
        self.wheel = wheel

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        # print( environ, file=sys.stderr )
        app = wsgiref.util.shift_path_info(environ)
        try:
            if app and app.lower() == "player":
                return self.player_app(environ, start_response)
            elif app and app.lower() == "bet":
                return self.bet_app(environ, start_response)
            elif app and app.lower() == "wheel":
                return self.wheel_app(environ, start_response)
            else:
                raise RESTException(
                    "404 NOT_FOUND",
                    "Unknown app in {SCRIPT_NAME}/{PATH_INFO}".format_map(environ),
                )
        except RESTException as e:
            status = e.args[0]
            headers = [("Content-type", "text/plain; charset=utf-8")]
            start_response(status, headers, sys.exc_info())
            return [repr(e.args).encode("UTF-8")]

    def player_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "GET":
            details = dict(stake=self.table.stake, rounds=self.rounds)
            status = "200 OK"
            headers = [("Content-type", "application/json; charset=utf-8")]
            start_response(status, headers)
            return [json.dumps(details).encode("UTF-8")]
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )

    def bet_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "GET":
            details = dict(self.table.bets)
        elif environ["REQUEST_METHOD"] == "POST":
            size = int(environ["CONTENT_LENGTH"])
            raw = environ["wsgi.input"].read(size).decode("UTF-8")
            try:
                data = json.loads(raw)
                if isinstance(data, dict):
                    data = [data]
                for detail in data:
                    self.table.place_bet(detail["bet"], int(detail["amount"]))
            except Exception as e:
                # Must undo all bets.
                raise RESTException(f"403 FORBIDDEN", "Bet {raw!r}")
            details = dict(self.table.bets)
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )
        status = "200 OK"
        headers = [("Content-type", "application/json; charset=utf-8")]
        start_response(status, headers)
        return [json.dumps(details).encode("UTF-8")]

    def wheel_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "POST":
            size = environ["CONTENT_LENGTH"]
            if size != "0":
                raw = environ["wsgi.input"].read(int(size))
                raise RESTException(
                    "403 FORBIDDEN", f"Data {raw!r} not allowed"
                )
            spin = self.wheel.spin()
            payout = self.table.resolve(spin)
            self.rounds += 1
            details = dict(
                spin=spin, payout=payout, stake=self.table.stake, rounds=self.rounds
            )
            status = "200 OK"
            headers = [("Content-type", "application/json; charset=utf-8")]
            start_response(status, headers)
            return [json.dumps(details).encode("UTF-8")]
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )


test_table = """
    Spike to show that the essential features work.
    
    >>> wheel = American(seed=2)
    >>> roulette = Roulette(wheel)
    >>> data = {"bet": "Black", "amount": 2}
    >>> roulette.table.place_bet(data["bet"], int(data["amount"]))
    >>> print(roulette.table.bets)
    defaultdict(<class 'int'>, {'Black': 2})
    >>> spin = wheel.spin()
    >>> payout = roulette.table.resolve(spin)
    >>> print(spin, payout)
    {'4': (35, 1), 'Black': (1, 1), 'Lo': (1, 1), 'Even': (1, 1)} [('Black', 2, 'win')]
"""

# Server
def roulette_server_3(count: int = 1) -> None:
    from wsgiref.simple_server import make_server
    from wsgiref.validate import validator

    wheel = American(seed=1)
    roulette = Roulette(wheel)
    debug = validator(roulette)
    httpd = make_server("", 8080, debug)
    if count is None:
        httpd.serve_forever()
    else:
        for c in range(count):
            httpd.handle_request()


# Client
import http.client
import json


def roulette_client(
    method: str = "GET",
    path: str = "/",
    data: Optional[Dict[str, str]] = None
) -> Response:
    rest = http.client.HTTPConnection("localhost", 8080)
    if data:
        header = {"Content-type": "application/json; charset=utf-8'"}
        params = json.dumps(data).encode("UTF-8")
        rest.request(method, path, params, header)
    else:
        rest.request(method, path)
    response = rest.getresponse()
    raw = response.read().decode("utf-8")
    try:
        document = json.loads(raw)
    except json.decoder.JSONDecodeError as ex:
        document = raw
    return Response(response.status, dict(response.getheaders()), document)


def server_3() -> None:
    import concurrent.futures
    import time

    with concurrent.futures.ProcessPoolExecutor() as executor:
        executor.submit(roulette_server_3, 4)
        time.sleep(0.1)  # Wait for the server to start
        r1 = roulette_client("GET", "/player/")
        print(r1)
        r2 = roulette_client("POST", "/bet/", {"bet": "Black", "amount": "2"})
        print(r2)
        r3 = roulette_client("GET", "/bet/")
        print(r3)
        r4 = roulette_client("POST", "/wheel/")
        print(r4)
        assert r1.status == 200 and r1.content == {"stake": 100, "rounds": 0}
        assert r2.status == 200 and r2.content == {"Black": 2}
        assert r3.status == 200 and r3.content == {"Black": 2}
        assert (
            r4.status == 200
            and r4.content == {'spin': {'9': [35, 1], 'Red': [1, 1], 'Lo': [1, 1], 'Odd': [1, 1]}, 'payout': [['Black', 2, 'lose']], 'stake': 98, 'rounds': 1}
        ), f"{r4!r}"


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)
    server_3()


================================================
FILE: Chapter_13/ch13_e1_ex4.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 4.
"""


# REST basics
# ========================================

# Stateless. Roulette.  Base class definitions.

from typing import Dict, Tuple, Optional, List, Iterable, Any, TYPE_CHECKING, cast

if TYPE_CHECKING:
    from wsgiref.types import WSGIApplication, WSGIEnvironment, StartResponse

import random
from Chapter_13.ch13_ex1 import (
    Wheel,
    Zero,
    DoubleZero,
    American,
    European,
    Response,
    json_get,
)


import sys
import wsgiref.util
import json


# REST Revised: Callable WSGI Applications
# =========================================


class Wheel2(Wheel):

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        winner = self.spin()
        status = "200 OK"
        headers = [("Content-type", "application/json; charset=utf-8")]
        start_response(status, headers)
        return [json.dumps(winner).encode("UTF-8")]


class American2(DoubleZero, Wheel2):
    pass


class European2(Zero, Wheel2):
    pass


# A WSGI wrapper application.
import sys


class Wheel3:

    def __init__(self):
        self.am = American2()
        self.eu = European2()

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        request = wsgiref.util.shift_path_info(environ)  # 1. Parse.
        print("Wheel3", request, file=sys.stderr)  # 2. Logging.
        if request and request.lower().startswith("eu"):  # 3. Evaluate.
            response = self.eu(environ, start_response)
        else:
            response = self.am(environ, start_response)
        return response  # 4. Respond.


# REST with sessions and state
# ========================================

# Player and Bet for Roulette.

# CRUD design issues.
# Player:
# - GET to see stake and rounds played.
# Bet:
# - POST to create a series of bets or decline to bet.
# - GET to see bets.
# Wheel:
# - GET to get spin and payout.

# Stateful object
from collections import defaultdict


class Table:

    def __init__(self, stake: int = 100) -> None:
        self.bets: Dict[str, int] = defaultdict(int)
        self.stake = stake

    def place_bet(self, name: str, amount: int) -> None:
        self.bets[name] += amount

    def clear_bets(self, name: str) -> None:
        self.bets: Dict[str, int] = defaultdict(int)

    def resolve(self, spin: Dict[str, Tuple[int, int]]) -> List[Tuple[str, int, str]]:
        """spin is a dict with bet:(x:y)."""
        details = []
        while self.bets:
            bet, amount = self.bets.popitem()
            if bet in spin:
                x, y = spin[bet]
                self.stake += int(amount * x / y)
                details.append((bet, amount, "win"))
            else:
                self.stake -= amount
                details.append((bet, amount, "lose"))
        return details


# WSGI Applications
class WSGI:

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        raise NotImplementedError


class RESTException(Exception):
    pass


class Roulette(WSGI):

    def __init__(self, wheel):
        self.table = Table(100)
        self.rounds = 0
        self.wheel = wheel

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        # print(environ, file=sys.stderr)
        app = wsgiref.util.shift_path_info(environ)
        try:
            if app and app.lower() == "player":
                return self.player_app(environ, start_response)
            elif app and app.lower() == "bet":
                return self.bet_app(environ, start_response)
            elif app and app.lower() == "wheel":
                return self.wheel_app(environ, start_response)
            else:
                raise RESTException(
                    "404 NOT_FOUND",
                    "Unknown app in {SCRIPT_NAME}/{PATH_INFO}".format_map(environ),
                )
        except RESTException as e:
            status = e.args[0]
            headers = [("Content-type", "text/plain; charset=utf-8")]
            start_response(status, headers, sys.exc_info())
            return [repr(e.args).encode("UTF-8")]

    def player_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "GET":
            details = dict(stake=self.table.stake, rounds=self.rounds)
            status = "200 OK"
            headers = [("Content-type", "application/json; charset=utf-8")]
            start_response(status, headers)
            return [json.dumps(details).encode("UTF-8")]
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )

    def bet_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "GET":
            details = dict(self.table.bets)
        elif environ["REQUEST_METHOD"] == "POST":
            size = int(environ["CONTENT_LENGTH"])
            raw = environ["wsgi.input"].read(size).decode("UTF-8")
            try:
                data = json.loads(raw)
                if isinstance(data, dict):
                    data = [data]
                for detail in data:
                    self.table.place_bet(detail["bet"], int(detail["amount"]))
            except Exception as e:
                # TODO: Must undo all bets.
                raise RESTException(f"403 FORBIDDEN", "Bet {raw!r}")
            details = dict(self.table.bets)
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )
        status = "200 OK"
        headers = [("Content-type", "application/json; charset=utf-8")]
        start_response(status, headers)
        return [json.dumps(details).encode("UTF-8")]

    def wheel_app(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if environ["REQUEST_METHOD"] == "POST":
            size = environ["CONTENT_LENGTH"]
            if size != "":
                raw = environ["wsgi.input"].read(int(size))
                raise RESTException(
                    "403 FORBIDDEN", f"Data '{raw!r}' not allowed"
                )
            spin = self.wheel.spin()
            payout = self.table.resolve(spin)
            self.rounds += 1
            details = dict(
                spin=spin, payout=payout, stake=self.table.stake, rounds=self.rounds
            )
            status = "200 OK"
            headers = [("Content-type", "application/json; charset=utf-8")]
            start_response(status, headers)
            return [json.dumps(details).encode("UTF-8")]
        else:
            raise RESTException(
                "405 METHOD_NOT_ALLOWED",
                "Method '{REQUEST_METHOD}' not allowed".format_map(environ),
            )


test_table = """
    Spike to show that the essential features work.
    
    >>> wheel = American(seed=2)
    >>> roulette = Roulette(wheel)
    >>> data = {"bet": "Black", "amount": 2}
    >>> roulette.table.place_bet(data["bet"], int(data["amount"]))
    >>> print(roulette.table.bets)
    defaultdict(<class 'int'>, {'Black': 2})
    >>> spin = wheel.spin()
    >>> payout = roulette.table.resolve(spin)
    >>> print(spin, payout)
    {'4': (35, 1), 'Black': (1, 1), 'Lo': (1, 1), 'Even': (1, 1)} [('Black', 2, 'win')]
"""

# Server
def roulette_server_4(count: int = 1):
    from wsgiref.simple_server import make_server
    from wsgiref.validate import validator

    wheel = American()
    roulette = Roulette(wheel)
    debug = validator(roulette)
    httpd = make_server("", 8080, debug)
    if count is None:
        httpd.serve_forever()
    else:
        for c in range(count):
            httpd.handle_request()


# Client
import http.client
import json


def roulette_client(method="GET", path="/", data=None):
    rest = http.client.HTTPConnection("localhost", 8080)
    if data:
        header = {"Content-type": "application/json; charset=utf-8'"}
        params = json.dumps(data).encode("UTF-8")
        rest.request(method, path, params, header)
    else:
        rest.request(method, path)
    response = rest.getresponse()
    raw = response.read().decode("utf-8")
    if 200 <= response.status < 300:
        document = json.loads(raw)
        return document
    else:
        print(response.status, response.reason)
        print(response.getheaders())
        print(raw)


# REST with authentication
# ========================================

# Authentication class definition with password hashing.
from hashlib import sha256
import os


class Authentication:
    iterations = 1000

    def __init__(self, username: bytes, password: bytes, salt: Optional[bytes]=None) -> None:
        """Works with bytes. Not Unicode strings."""
        self.username = username
        self.salt = salt or os.urandom(24)
        self.hash = self._iter_hash(self.iterations, self.salt, username, password)

    @staticmethod
    def _iter_hash(iterations: int, salt: bytes, username: bytes, password: bytes):
        seed = salt + b":" + username + b":" + password
        for i in range(iterations):
            seed = sha256(seed).digest()
        return seed

    def __eq__(self, other: Any) -> bool:
        other = cast("Authentication", other)
        return self.username == other.username and self.hash == other.hash

    def __hash__(self) -> int:
        return hash(self.hash)

    def __repr__(self) -> str:
        salt_x = "".join("{0:x}".format(b) for b in self.salt)
        hash_x = "".join("{0:x}".format(b) for b in self.hash)
        return f"{self.username} {self.iterations:d}:{salt_x}:{hash_x}"

    def match(self, password: bytes) -> bool:
        test = self._iter_hash(self.iterations, self.salt, self.username, password)
        return self.hash == test  # Constant Time is Best


# Collection of users.
class Users(dict):

    def __init__(self, *args, **kw) -> None:
        super().__init__(*args, **kw)
        # Can never be found -- dict key is invalid and isn't the username.
        self[""] = Authentication(b"__dummy__", b"Doesn't Matter")

    def add(self, authentication: Authentication) -> None:
        if authentication.username == "":
            raise KeyError("Invalid Authentication")
        self[authentication.username] = authentication

    def match(self, username: bytes, password: bytes) -> bool:
        if username in self and username != "":
            return self[username].match(password)
        else:
            # Time-wasting comparison
            return self[""].match(b"Something which doesn't match")


# Global Objects
users = Users()
users.add(Authentication(b"Aladdin", b"open sesame"))

test_matching = """
    Spike to show user matching rule.
    
    >>> test_salt = bytes(range(24))
    >>> al = Authentication(b"Aladdin", b"open sesame", test_salt)
    >>> al
    b'Aladdin' 1000:0123456789abcdef1011121314151617:a53bdcd6d16acc8fd33fc982c973147f15f6ce43cff4fb83a5f6b267de1
    
    >>> users = Users()
    >>> users.add(Authentication(b"Aladdin", b"open sesame"))

    >>> users.match("", b"Doesn't Matter")
    False
    >>> users.match(b"__dummy__", b"Doesn't Matter")
    False
"""

# Authentication app
import base64


class Authenticate(WSGI):

    def __init__(self, users, target_app):
        self.users = users
        self.target_app = target_app

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        if "HTTP_AUTHORIZATION" in environ:
            scheme, credentials = environ["HTTP_AUTHORIZATION"].split()
            if scheme == "Basic":
                username, password = base64.b64decode(credentials).split(b":")
                if self.users.match(username, password):
                    environ["Authenticate.username"] = username
                    return self.target_app(environ, start_response)
        status = "401 UNAUTHORIZED"
        headers = [
            ("Content-Type", "text/plain; charset=utf-8"),
            ("WWW-Authenticate", 'Basic realm="roulette@localhost"'),
        ]
        start_response(status, headers)
        return ["Not authorized".encode("utf-8")]


# Some app which requires authentication
class Some_App(WSGI):

    def __call__(
        self, environ: "WSGIEnvironment", start_response: "StartResponse"
    ) -> Iterable[bytes]:
        status = "200 OK"
        headers = [("Content-type", "text/plain; charset=utf-8")]
        start_response(status, headers)
        return ["Welcome".encode("UTF-8")]


# Demo client
import base64


def authenticated_client(
    method: str = "GET",
    path: str = "/",
    data: Optional[str] = None,
    username: str = "",
    password: str = "",
) -> Tuple[int, str, str]:
    rest = http.client.HTTPConnection("localhost", 8080)
    headers = {}
    if username and password:
        enc = base64.b64encode(
            username.encode("ascii") + b":" + password.encode("ascii")
        )
        headers["Authorization"] = f"Basic {enc.decode('ascii')}"
    if data:
        headers["Content-type"] = "application/json; charset=utf-8"
        params = json.dumps(data).encode("utf-8")
        rest.request(method, path, params, headers=headers)
    else:
        rest.request(method, path, headers=headers)
    # print(f"*** CLIENT: {headers}")
    response = rest.getresponse()
    raw = response.read().decode("utf-8")
    if response.status == 401:
        print(response.getheaders())
    return response.status, response.reason, raw


# Server
def auth_server(count: int = 1) -> None:
    from wsgiref.simple_server import make_server
    from wsgiref.validate import validator

    secure_app = Some_App()
    authenticated_app = Authenticate(users, secure_app)
    debug = validator(authenticated_app)
    httpd = make_server("", 8080, debug)
    if count is None:
        httpd.serve_forever()
    else:
        for c in range(count):
            httpd.handle_request()


# Demo
def server_5() -> None:
    import concurrent.futures
    import time

    with concurrent.futures.ProcessPoolExecutor() as executor:
        executor.submit(auth_server, 3)
        time.sleep(0.1)  # Wait for the server to start
        print(authenticated_client("GET", "/player/"))
        print(
            authenticated_client(
                "GET", "/player/", username="Aladdin", password="open sesame"
            )
        )
        print(
            authenticated_client(
                "GET", "/player/", username="Aladdin", password="not right"
            )
        )


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)
    server_5()


================================================
FILE: Chapter_13/ch13_ex1.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 1.
"""


# REST basics
# ========================================

# Object and state

test_example_1 = """
>>> from dataclasses import dataclass, asdict
>>> import json

>>> @dataclass
... class Greeting:
...     message: str
    
>>> g = Greeting("Hello World")
>>> text = json.dumps(asdict(g))
>>> text
'{"message": "Hello World"}'
>>> text.encode('utf-8')
b'{"message": "Hello World"}'
"""

# Stateless Roulette Server
# ==========================

# Base class definitions.

from typing import (
    Dict,
    Tuple,
    Optional,
    Callable,
    List,
    Union,
    Iterator,
    NamedTuple,
    Any,
    Type,
    Iterable,
    TYPE_CHECKING,
)
from abc import abstractmethod
import random


class Wheel:
    """Abstract, zero bins omitted."""

    def __init__(self, seed: Optional[int] = None) -> None:
        self.rng = random.Random()
        self.rng.seed(seed)
        self.bins = [
            {
                str(n): (35, 1),
                self.redblack(n): (1, 1),
                self.hilo(n): (1, 1),
                self.evenodd(n): (1, 1),
            }
            for n in range(1, 37)
        ]
        self.bins.extend(self.zero())

    @abstractmethod
    def zero(self) -> List[Dict[str, Tuple[int, int]]]:
        pass

    @staticmethod
    def redblack(n: int) -> str:
        return "Red" if n in (
            1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36
        ) else "Black"

    @staticmethod
    def hilo(n: int) -> str:
        return "Hi" if n >= 19 else "Lo"

    @staticmethod
    def evenodd(n: int) -> str:
        return "Even" if n % 2 == 0 else "Odd"

    def spin(self) -> Dict[str, Tuple[int, int]]:
        return self.rng.choice(self.bins)


class Zero:

    def zero(self) -> List[Dict[str, Tuple[int, int]]]:
        return [{"0": (35, 1)}]


class DoubleZero(Zero):

    def zero(self) -> List[Dict[str, Tuple[int, int]]]:
        z_bins = super().zero()
        z_bins += [{"00": (35, 1)}]
        return z_bins


class American(DoubleZero, Wheel):
    pass


class European(Zero, Wheel):
    pass


# Some global objects used by a WSGI application function
american = American(9973)
european = European(9973)

test_demonstrate_wheel = """
    >>> american.bins[-2:]
    [{'0': (35, 1)}, {'00': (35, 1)}]
    >>> european.bins[-1]
    {'0': (35, 1)}

    >>> for i in range(7):
    ...     print(american.spin())
    {'25': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}
    {'18': (35, 1), 'Red': (1, 1), 'Lo': (1, 1), 'Even': (1, 1)}
    {'20': (35, 1), 'Black': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'21': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}
    {'32': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'34': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'21': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}

    >>> for i in range(7):
    ...     print(european.spin())
    {'25': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}
    {'18': (35, 1), 'Red': (1, 1), 'Lo': (1, 1), 'Even': (1, 1)}
    {'20': (35, 1), 'Black': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'21': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}
    {'32': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'34': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Even': (1, 1)}
    {'21': (35, 1), 'Red': (1, 1), 'Hi': (1, 1), 'Odd': (1, 1)}
"""

import sys
import wsgiref.util
import json

if TYPE_CHECKING:
    from wsgiref.types import WSGIApplication, WSGIEnvironment, StartResponse


def wsgi_wheel(
    environ: "WSGIEnvironment", start_response: "StartResponse"
) -> Iterable[bytes]:
    request = wsgiref.util.shift_path_info(environ)  # 1. Parse.
    print("wheel", repr(request), file=sys.stderr)  # 2. Logging.
    if request and request.lower().startswith("eu"):  # 3. Evaluate.
        winner = european.spin()
    else:
        winner = american.spin()
    status = "200 OK"  # 4. Respond.
    headers = [("Content-Type", "text/plain; charset=utf-8")]
    start_response(status, headers)
    return [json.dumps(winner).encode("UTF-8")]


# A function we can call to start a server
# which handles a finite number of requests.
# Handy for testing.
from wsgiref.simple_server import make_server


def roulette_server(count: int = 1) -> None:
    httpd = make_server("", 8080, wsgi_wheel)
    if count is None:
        httpd.serve_forever()
    else:
        for c in range(count):
            httpd.handle_request()


# REST Client
# -------------

# A REST client that simply loads a JSON document.
import http.client
import json
from typing import NamedTuple


class Response(NamedTuple):
    status: int
    headers: Dict[str, str]
    content: Optional[Any]

    def __str__(self) -> str:
        return f"{self.status}: {self.content}"


def json_get(path: str = "/") -> Response:
    rest = http.client.HTTPConnection("localhost", 8080, timeout=5)
    rest.request("GET", path)
    response = rest.getresponse()
    # print(f"client: {response.status} {response.reason}")
    # print(f"  {response.getheaders()}")
    raw = response.read().decode("utf-8")
    # print(f"  {raw}")
    try:
        document = json.loads(raw)
    except json.decoder.JSONDecodeError as ex:
        document = None
    return Response(response.status, dict(response.getheaders()), document)


# Roulette Demo
# --------------

# When run as the main script, start a server and interact with it.
# Note that the subprocess will inherit the state of the wheel from the parent
# process; the results are therefore based on the seed, and deterministic.


def server() -> None:
    import concurrent.futures
    import time

    with concurrent.futures.ProcessPoolExecutor() as executor:
        # We'll make four requests. This allows for a very clean termination of a test server.
        srvr = executor.submit(roulette_server, 4)
        time.sleep(0.1)  # Wait for the server to start
        r1 = json_get()
        r2 = json_get()
        r3 = json_get("/european/")
        r4 = json_get("/european/")
        assert (
            str(r1)
            == "200: {'22': [35, 1], 'Black': [1, 1], 'Hi': [1, 1], 'Even': [1, 1]}"
        )
        assert (
            str(r2)
            == "200: {'15': [35, 1], 'Black': [1, 1], 'Lo': [1, 1], 'Odd': [1, 1]}"
        )
        assert (
            str(r3)
            == "200: {'22': [35, 1], 'Black': [1, 1], 'Hi': [1, 1], 'Even': [1, 1]}"
        )
        assert (
            str(r4)
            == "200: {'15': [35, 1], 'Black': [1, 1], 'Lo': [1, 1], 'Odd': [1, 1]}"
        )


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=True)
    server()


================================================
FILE: Chapter_13/ch13_ex2.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 2.
"""


# Problem Domain
# ==============


from dataclasses import dataclass, asdict, astuple
from typing import List, Dict, Any, Tuple, NamedTuple
import random


@dataclass(frozen=True)
class Domino:
    v_0: int
    v_1: int

    @property
    def double(self):
        return self.v_0 == self.v_1

    def __repr__(self):
        if self.double:
            return f"Double({self.v_0})"
        else:
            return f"Domino({self.v_0}, {self.v_1})"


class Boneyard:
    """
    >>> random.seed(2)
    >>> b = Boneyard(limit=6)
    >>> len(b._dominoes)
    28
    >>> b.deal(tiles=7, hands=2)
    [[Domino(2, 0), Double(5), Domino(5, 2), Domino(5, 0), Double(0), Domino(6, 3), Domino(2, 1)], [Domino(3, 1), Double(4), Domino(5, 1), Domino(5, 4), Domino(6, 2), Domino(4, 2), Domino(5, 3)]]

    """

    def __init__(self, limit=6):
        self._dominoes = [
            Domino(x, y) for x in range(0, limit + 1) for y in range(0, x + 1)
        ]
        random.shuffle(self._dominoes)

    def deal(self, tiles: int = 7, hands: int = 4) -> List[List[Tuple[int, int]]]:
        if tiles * hands > len(self._dominoes):
            raise ValueError(f"Invalid tiles={tiles}, hands={hands}")
        return [self._dominoes[h:h + tiles] for h in range(0, tiles * hands, tiles)]


# FLASK Restful Web Service
# =========================

from typing import Dict, Any, Tuple

from flask import Flask, jsonify, abort
from http import HTTPStatus

# Application Server

app = Flask(__name__)


@app.route("/dominoes/<n>")
def dominoes(n: str) -> Tuple[Dict[str, Any], int]:
    try:
        hand_size = int(n)
    except ValueError:
        abort(HTTPStatus.BAD_REQUEST)

    if app.env == "development":
        random.seed(2)
    b = Boneyard(limit=6)
    hand_0 = b.deal(hand_size)[0]
    app.logger.info("Send %r", hand_0)

    return jsonify(status="OK", dominoes=[astuple(d) for d in hand_0]), HTTPStatus.OK


@app.route("/hands/<int:h>/dominoes/<int:c>")
def hands(h: int, c: int) -> Tuple[Dict[str, Any], int]:
    if h == 0 or c == 0:
        return jsonify(
            status="Bad Request", error=[f"hands={h!r}, dominoes={c!r} is invalid"]
        ), HTTPStatus.BAD_REQUEST

    if app.env == "development":
        random.seed(2)
    b = Boneyard(limit=6)
    try:
        hand_list = b.deal(c, h)
    except ValueError as ex:
        return jsonify(status="Bad Request", error=ex.args), HTTPStatus.BAD_REQUEST
    app.logger.info("Send %r", hand_list)

    return jsonify(
        status="OK", dominoes=[[astuple(d) for d in hand] for hand in hand_list]
    ), HTTPStatus.OK


OPENAPI_SPEC = {
    "openapi": "3.0.0",
    "info": {
        "description": "Deals simple hands of dominoes",
        "version": "2019.02",
        "title": "Chapter 13. Example 2",
    },
    "paths": {},
}


@app.route("/openapi.json")
def openapi() -> Dict[str, Any]:
    """
    >>> client = app.test_client()
    >>> response = client.get("/openapi.json")
    >>> response.get_json()['openapi']
    '3.0.0'
    >>> response.get_json()['info']['title']
    'Chapter 13. Example 2'
    """
    # See dominoes_openapi.json for full specification
    return jsonify(OPENAPI_SPEC)


test_openapi_spec = """
    >>> random.seed(2)
    >>> client = app.test_client()
    >>> response = client.get("/openapi.json")
    >>> response.get_json()['openapi']
    '3.0.0'
    >>> response.get_json()['info']['title']
    'Chapter 13. Example 2'
    
    >>> response = client.get("/dominoes/5")
    >>> response.status
    '200 OK'
    >>> response.status_code
    200
    >>> response.get_json()
    {'dominoes': [[2, 0], [5, 5], [5, 2], [5, 0], [0, 0]], 'status': 'OK'}
    
    >>> document = response.get_json()
    >>> hand = list(Domino(*d) for d in document['dominoes'])
    >>> hand
    [Domino(2, 0), Double(5), Domino(5, 2), Domino(5, 0), Double(0)]
    
    >>> response = client.get("hands/2/dominoes/7")
    >>> response.status
    '200 OK'
    >>> response.status_code
    200
    >>> document = response.get_json()
    >>> document
    {'dominoes': [[[5, 3], [1, 0], [4, 1], [3, 3], [2, 1], [2, 0], [3, 0]], [[5, 4], [4, 4], [6, 3], [6, 5], [5, 0], [6, 4], [3, 2]]], 'status': 'OK'}

    >>> hands = list(list(Domino(*d) for d in h) for h in document['dominoes'])
    >>> for player, h in enumerate(hands):
    ...     for d in h:
    ...         if d.double:
    ...             print(player, d)
    0 Double(3)
    1 Double(4)
    
    >>> response = client.get("hands/nope/dominoes/7")
    >>> response.status
    '404 NOT FOUND'
    >>> response = client.get("hands/0/dominoes/nope")
    >>> response.status
    '404 NOT FOUND'

    >>> response = client.get("hands/0/dominoes/7")
    >>> response.status
    '400 BAD REQUEST'
    >>> response.get_json()
    {'error': ['hands=0, dominoes=7 is invalid'], 'status': 'Bad Request'}
    >>> response = client.get("hands/4/dominoes/0")
    >>> response.status
    '400 BAD REQUEST'
    >>> response.get_json()
    {'error': ['hands=4, dominoes=0 is invalid'], 'status': 'Bad Request'}

    >>> response = client.get("hands/7/dominoes/7")
    >>> response.status
    '400 BAD REQUEST'
    >>> response.get_json()
    {'error': ['Invalid tiles=7, hands=7'], 'status': 'Bad Request'}
"""


__test__ = {name: value for name, value in locals().items() if name.startswith("test_")}

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=False)


================================================
FILE: Chapter_13/ch13_ex3.py
================================================
#!/usr/bin/env python3.7
"""
Mastering Object-Oriented Python 2e

Code Examples for Mastering Object-Oriented Python 2nd Edition

Chapter 13. Example 3.
"""


# Problem Domain
# ==============

from typing import Dict, Any, Tuple, List
from dataclasses import dataclass, asdict
import random
import secrets
from enum import Enum


class Status(str, Enum):
    UPDATED = "Updated"
    CREATED = "Created"


@dataclass
class Dice:
    roll: List[int]
    identifier: str
    status: str

    def reroll(self, keep_positions: List[int]) -> None:
        for i in range(len(self.roll)):
            if i not in keep_positions:
                self.roll[i] = random.randint(1, 6)
        self.status = Status.UPDATED


def make_dice(n_dice: int) -> Dice:
    # Could also be a @classmethod
    return Dice(
        roll=[random.randint(1, 6) for _ in range(n_dice)],
        identifier=secrets.token_urlsafe(8),
        status=Status.CREATED,
    )


# FLASK Restful Web Service
# =========================


from flask import Flask, jsonify, request, url_for, Blueprint, current_app, abort
from typing import Dict, Any, Tuple, List
from http import HTTPStatus

OPENAPI_SPEC = {
    "openapi": "3.0.0",
    "info": {
        "title": "Chapter 13. Example 3",
        "version": "2019.02",
        "description": "Rolls dice",
    },
    "paths": {
        "/rolls": {
            "post": {
                "description": "first roll",
                "responses": {201: {"description": "Success"}},
            },
            "get": {
                "description": "current s
Download .txt
gitextract_if6v5z7l/

├── .pylintrc
├── Chapter_1/
│   ├── ch01_ex1.py
│   ├── ch01_ex2.py
│   ├── ch01_ex3.py
│   ├── ch01_ex4.py
│   ├── ch01_ex5.py
│   └── getting_started.rst
├── Chapter_10/
│   ├── __init__.py
│   ├── ch10_bonus.py
│   ├── ch10_ex1.py
│   ├── ch10_ex2.py
│   ├── ch10_ex2a.py
│   ├── ch10_ex2b.py
│   ├── ch10_ex2c.py
│   ├── ch10_ex3.py
│   ├── ch10_ex4.py
│   ├── ch10_ex5.py
│   └── ch10_ex6.py
├── Chapter_11/
│   ├── __init__.py
│   ├── ch11_ex1.py
│   └── ch11_ex2.py
├── Chapter_12/
│   ├── __init__.py
│   ├── ch12_ex1.py
│   ├── ch12_ex2.py
│   ├── ch12_ex3.py
│   └── ch12_ex4.py
├── Chapter_13/
│   ├── __init__.py
│   ├── cards_openapi.json
│   ├── ch13_e1_ex2.py
│   ├── ch13_e1_ex3.py
│   ├── ch13_e1_ex4.py
│   ├── ch13_ex1.py
│   ├── ch13_ex2.py
│   ├── ch13_ex3.py
│   ├── ch13_ex4.py
│   ├── ch13_ex5.py
│   ├── ch13_ex6.py
│   ├── dice_openapi.json
│   ├── dominoes_openapi.json
│   └── simulation_model.py
├── Chapter_14/
│   ├── __init__.py
│   ├── ch14_ex1.py
│   ├── ch14_ex2.py
│   ├── ch14_ex3.py
│   ├── ch14_ex4.py
│   ├── ch14_ex5.py
│   ├── ch14_ex6.py
│   ├── simulation_model.py
│   └── someapp.config
├── Chapter_15/
│   ├── __init__.py
│   ├── ch15_ex1.py
│   └── ch15_ex2.py
├── Chapter_16/
│   ├── __init__.py
│   ├── ch16_ex1.py
│   ├── ch16_ex10.py
│   ├── ch16_ex2.py
│   ├── ch16_ex3.py
│   ├── ch16_ex4.py
│   ├── ch16_ex5.py
│   ├── ch16_ex6.py
│   ├── ch16_ex7.py
│   ├── ch16_ex8.py
│   └── ch16_ex9.py
├── Chapter_17/
│   ├── __init__.py
│   ├── ch17_data.csv
│   ├── ch17_ex1.py
│   ├── ch17_ex2.py
│   └── test_ch17.py
├── Chapter_18/
│   ├── __init__.py
│   ├── ch18_demo.py
│   ├── ch18_ex1.py
│   ├── ch18_ex2.py
│   ├── ch18_ex3.py
│   ├── ch18app.yaml
│   └── opt/
│       └── ch18app.yaml
├── Chapter_19/
│   ├── __init__.py
│   ├── ch19_ex1.py
│   ├── ch19_ex2.py
│   ├── some_algorithm/
│   │   ├── __init__.py
│   │   ├── abstraction.py
│   │   ├── long_version.py
│   │   └── short_version.py
│   └── tests/
│       ├── __init__.py
│       └── test_all.py
├── Chapter_2/
│   ├── __init__.py
│   ├── ch02_ex1.py
│   ├── ch02_ex2.py
│   ├── ch02_ex3.py
│   ├── ch02_ex4.py
│   └── ch02_ex5.py
├── Chapter_20/
│   ├── README.rst
│   ├── __init__.py
│   ├── combo.py
│   ├── combo.py.html
│   ├── combo.py.txt
│   ├── docs/
│   │   ├── Makefile
│   │   ├── conf.py
│   │   ├── implementation.rst
│   │   ├── index.rst
│   │   └── user_story.rst
│   ├── src/
│   │   ├── __init__.py
│   │   └── ch20_ex1.py
│   └── tests/
│       └── test_ch20.py
├── Chapter_3/
│   ├── __init__.py
│   ├── ch03_ex1.py
│   ├── ch03_ex2.py
│   ├── ch03_ex3.py
│   ├── ch03_ex4.py
│   └── ch03_ex5.py
├── Chapter_4/
│   ├── __init__.py
│   ├── ch04_ex1.py
│   ├── ch04_ex2.py
│   ├── ch04_ex3.py
│   ├── ch04_ex4.py
│   └── ch04_ex5.py
├── Chapter_5/
│   ├── __init__.py
│   ├── ch05_ex1.py
│   └── ch05_ex2.py
├── Chapter_6/
│   ├── __init__.py
│   ├── ch06_ex1.py
│   └── ch06_ex2.py
├── Chapter_7/
│   ├── __init__.py
│   ├── ch07_defaults.json
│   ├── ch07_ex1.py
│   ├── ch07_ex2.py
│   ├── ch07_ex3.py
│   └── ch07_ex4.py
├── Chapter_8/
│   ├── __init__.py
│   └── ch08_ex1.py
├── Chapter_9/
│   ├── __init__.py
│   ├── ch09_ex1.py
│   └── ch09_ex2.py
├── LICENSE
├── README.md
├── data/
│   ├── ch17_data.csv
│   └── ch17_sample.csv
├── environment.yaml
├── requirements.txt
├── show_hierarchies.py
├── stubs/
│   └── sqlite3.pyi
├── test_all.py
└── tox.ini
Download .txt
SYMBOL INDEX (1344 symbols across 85 files)

FILE: Chapter_1/ch01_ex3.py
  function F (line 10) | def F(n: int) -> int:
  function demo (line 21) | def demo():

FILE: Chapter_1/ch01_ex4.py
  function factorial (line 13) | def factorial(n: int) -> int:

FILE: Chapter_1/ch01_ex5.py
  class EmptyClass (line 14) | class EmptyClass:

FILE: Chapter_10/ch10_bonus.py
  class Suit (line 17) | class Suit(Enum):
  class Card (line 23) | class Card:
    method __init__ (line 25) | def __init__(self, rank: str, suit: Suit, hard: Optional[int]=None, so...
    method __str__ (line 31) | def __str__(self) -> str:
  class AceCard (line 35) | class AceCard(Card):
    method __init__ (line 37) | def __init__(self, rank: str, suit: Suit) -> None:
  class FaceCard (line 41) | class FaceCard(Card):
    method __init__ (line 43) | def __init__(self, rank: str, suit: Suit) -> None:
  function card (line 46) | def card(rank: int, suit: Suit) -> Card:
  class Deck (line 55) | class Deck(list):
    method __init__ (line 56) | def __init__(self) -> None:
  class Hand (line 60) | class Hand(list):
    method hard (line 62) | def hard(self) -> int:
    method soft (line 66) | def soft(self) -> int:
    method __repr__ (line 69) | def __repr__(self) -> str:
  function deal_rules (line 73) | def deal_rules(deck: Deck) -> Tuple[Hand, Optional[int]]:
  function simulation (line 88) | def simulation() -> None:

FILE: Chapter_10/ch10_ex1.py
  class Post (line 24) | class Post:
    method as_dict (line 30) | def as_dict(self) -> Dict[str, Any]:
  class Blog_x (line 45) | class Blog_x(list):
    method __init__ (line 47) | def __init__(self, title: str, posts: Optional[List[Post]]=None) -> None:
    method by_tag (line 51) | def by_tag(self) -> DefaultDict[str, List[Dict[str, Any]]]:
    method as_dict (line 58) | def as_dict(self) -> Dict[str, Any]:
  function blogx_encode (line 120) | def blogx_encode(object: Any) -> Dict[str, Any]:
  function blogx_decode (line 155) | def blogx_decode(some_dict: Dict[str, Any]) -> Dict[str, Any]:
  class Blog (line 239) | class Blog:
    method __init__ (line 241) | def __init__(self, title: str, posts: Optional[List[Post]]=None) -> None:
    method underline (line 246) | def underline(self) -> str:
    method append (line 249) | def append(self, post: Post) -> None:
    method by_tag (line 252) | def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:
    method as_dict (line 259) | def as_dict(self) -> Dict[str, Any]:
  function blog_encode (line 287) | def blog_encode(object: Any) -> Dict[str, Any]:
  function blog_decode (line 320) | def blog_decode(some_dict: Dict[str, Any]) -> Dict[str, Any]:
  function rst_render (line 407) | def rst_render(blog: Blog) -> None:
  class Post_J (line 574) | class Post_J(Post):
    method _json (line 577) | def _json(self) -> Dict[str, Any]:
  class Blog_J (line 586) | class Blog_J(Blog):
    method _json (line 590) | def _json(self) -> Dict[str, Any]:
  function blog_j_encode (line 597) | def blog_j_encode(object: Union[Blog_J, Post_J, Any]) -> Dict[str, Any]:
  function blog_j2_encode (line 711) | def blog_j2_encode(object: Union[Blog_J, Post_J, Any]) -> Dict[str, Any]:

FILE: Chapter_10/ch10_ex2.py
  class Suit (line 19) | class Suit(str, Enum):
  class Card (line 25) | class Card:
    method __init__ (line 27) | def __init__(self, rank: str, suit: Suit, hard: Optional[int]=None, so...
    method __str__ (line 33) | def __str__(self) -> str:
  class AceCard (line 37) | class AceCard(Card):
    method __init__ (line 39) | def __init__(self, rank: str, suit: Suit) -> None:
  class FaceCard (line 43) | class FaceCard(Card):
    method __init__ (line 45) | def __init__(self, rank: str, suit: Suit) -> None:

FILE: Chapter_10/ch10_ex2b.py
  function card_representer (line 28) | def card_representer(dumper: Any, card: Card) -> str:
  function acecard_representer (line 33) | def acecard_representer(dumper: Any, card: Card) -> str:
  function facecard_representer (line 38) | def facecard_representer(dumper: Any, card: Card) -> str:
  function card_constructor (line 43) | def card_constructor(loader: Any, node: Any) -> Card:
  function acecard_constructor (line 49) | def acecard_constructor(loader: Any, node: Any) -> Card:
  function facecard_constructor (line 55) | def facecard_constructor(loader: Any, node: Any) -> Card:

FILE: Chapter_10/ch10_ex2c.py
  class Card2 (line 27) | class Card2(yaml.YAMLObject):
    method __init__ (line 31) | def __init__(self, rank, suit, hard=None, soft=None) -> None:
    method __str__ (line 37) | def __str__(self) -> str:
  class AceCard2 (line 41) | class AceCard2(Card2):
    method __init__ (line 44) | def __init__(self, rank, suit) -> None:
  class FaceCard2 (line 48) | class FaceCard2(Card2):
    method __init__ (line 51) | def __init__(self, rank, suit) -> None:

FILE: Chapter_10/ch10_ex3.py
  class Hand_bad (line 53) | class Hand_bad:
    method __init__ (line 55) | def __init__(self, dealer_card: Card, *cards: Card) -> None:
    method append (line 61) | def append(self, card: Card) -> None:
    method __str__ (line 65) | def __str__(self) -> str:
  class Hand2 (line 89) | class Hand2:
    method __init__ (line 91) | def __init__(self, dealer_card: Card, *cards: Card) -> None:
    method append (line 97) | def append(self, card: Card) -> None:
    method __str__ (line 101) | def __str__(self) -> str:
    method __getstate__ (line 105) | def __getstate__(self) -> Dict[str, Any]:
    method __setstate__ (line 108) | def __setstate__(self, state: Dict[str, Any]) -> None:
  class RestrictedUnpickler (line 136) | class RestrictedUnpickler(pickle.Unpickler):
    method find_class (line 138) | def find_class(self, module: str, name: str) -> Any:

FILE: Chapter_10/ch10_ex4.py
  class Table (line 30) | class Table:
    method bet (line 33) | def bet(self, game_state: str, amount: float) -> None:
  class Player_Strategy (line 38) | class Player_Strategy:
  class Betting (line 43) | class Betting:
    method __init__ (line 45) | def __init__(self, stake: float = 100) -> None:
    method bet (line 48) | def bet(self, table: Table, game_state: str) -> None:
    method win (line 52) | def win(self, amount: float) -> None:
    method loss (line 55) | def loss(self, amount: float) -> None:
    method push (line 58) | def push(self) -> None:
  class Flat_Bet (line 62) | class Flat_Bet(Betting):
  class Martingale_Bet (line 66) | class Martingale_Bet(Betting):
    method __init__ (line 68) | def __init__(self, *args) -> None:
    method bet (line 72) | def bet(self, table: Table, game_state: str) -> None:
    method win (line 80) | def win(self, amount) -> None:
    method loss (line 84) | def loss(self, amount) -> None:
    method push (line 88) | def push(self) -> None:
  class BadBet (line 95) | class BadBet(Exception):
  class Broke (line 99) | class Broke(Exception):
  class Blackjack (line 104) | class Blackjack(Table):
    method __init__ (line 106) | def __init__(self, play: Player_Strategy, betting: Betting) -> None:
    method stake (line 113) | def stake(self) -> float:
    method bet (line 116) | def bet(self, game_state: str, amount: float) -> None:
    method play_1 (line 121) | def play_1(self) -> None:
    method until_broke_or_rounds (line 137) | def until_broke_or_rounds(self, limit: int) -> None:
  class GameStat (line 150) | class GameStat(NamedTuple):
  function gamestat_iter (line 160) | def gamestat_iter(
  function gamestat_rdr_iter (line 197) | def gamestat_rdr_iter(
  function blog_builder (line 282) | def blog_builder(row: List[str]) -> Blog:
  function post_builder (line 286) | def post_builder(row: List[str]) -> Post:
  function blog_iter (line 316) | def blog_iter(source: TextIO) -> Iterator[Blog]:
  function post_builder5 (line 365) | def post_builder5(row: Dict[str, str]) -> Post:
  function blog_builder5 (line 374) | def blog_builder5(row: Dict[str, str]) -> Blog:
  function blog_iter2 (line 381) | def blog_iter2(source: TextIO) -> Iterator[Blog]:

FILE: Chapter_10/ch10_ex5.py
  class FixedField (line 38) | class FixedField(NamedTuple):
  class Metadata (line 44) | class Metadata(NamedTuple):
  function gamestat_record (line 59) | def gamestat_record(gamestat:GameStat, metadata: Metadata) -> str:
  function line_iter (line 79) | def line_iter(aFile: TextIO, metadata: Union[Metadata, 'XMetadata']) -> ...
  function record_iter (line 87) | def record_iter(aFile: TextIO, metadata: Metadata) -> Iterator[Dict[str,...
  function ebcdic_lookup (line 153) | def ebcdic_lookup(name, fallback=codecs.lookup):  # real signature unknown
  function alpha_decode (line 163) | def alpha_decode(data: bytes, metadata: 'XMetadata', field_metadata: 'XF...
  function display_decode (line 185) | def display_decode(data: bytes, metadata: 'XMetadata', field_metadata: '...
  function comp3_decode (line 209) | def comp3_decode(data: bytes, metadata: 'XMetadata', field_metadata: 'XF...
  function alpha_encode (line 241) | def alpha_encode(data: Any, metadata: 'XMetadata', field_metadata: 'XFie...
  function display_encode (line 265) | def display_encode(data: Decimal, metadata: 'XMetadata', field_metadata:...
  function comp3_encode (line 294) | def comp3_encode(data: Decimal, metadata: 'XMetadata', field_metadata: '...
  class XField (line 343) | class XField(NamedTuple):
  class XMetadata (line 350) | class XMetadata(NamedTuple):
    method decode (line 356) | def decode(self) -> Callable[[bytes], Tuple[str, int]]:
    method encode (line 360) | def encode(self) -> Callable[[str], Tuple[bytes, int]]:
  function gamestat_record_comp3 (line 376) | def gamestat_record_comp3(gamestat: GameStat, metadata: XMetadata) -> by...
  function record2_iter (line 395) | def record2_iter(aFile: TextIO, metadata: XMetadata) -> Iterator[Dict[st...

FILE: Chapter_10/ch10_ex6.py
  class Post_X (line 30) | class Post_X:
    method __post_init__ (line 38) | def __post_init__(self) -> None:
    method as_dict (line 42) | def as_dict(self) -> Dict[str, Any]:
    method xml (line 45) | def xml(self) -> str:
  class Blog_X (line 59) | class Blog_X:
    method __post_init__ (line 64) | def __post_init__(self) -> None:
    method append (line 67) | def append(self, post: Post_X) -> None:
    method by_tag (line 70) | def by_tag(self) -> DefaultDict[str, List[Dict[str, Any]]]:
    method as_dict (line 77) | def as_dict(self) -> Dict[str, Any]:
    method xml (line 80) | def xml(self) -> str:
  class Blog_E (line 135) | class Blog_E(Blog_X):
    method xmlelt (line 137) | def xmlelt(self) -> XML.Element:
  class Post_E (line 148) | class Post_E(Post_X):
    method xmlelt (line 150) | def xmlelt(self) -> XML.Element:
  function build_blog (line 195) | def build_blog(document: XML.ElementTree) -> Blog_X:

FILE: Chapter_11/ch11_ex1.py
  class Post (line 26) | class Post:
  class Blog (line 34) | class Blog:
    method __post_init__ (line 43) | def __post_init__(self) -> None:
    method append (line 46) | def append(self, post: Post) -> None:
    method by_tag (line 49) | def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:

FILE: Chapter_11/ch11_ex2.py
  class Post (line 21) | class Post:
    method __post_init__ (line 34) | def __post_init__(self) -> None:
  class Blog (line 39) | class Blog:
    method __post_init__ (line 48) | def __post_init__(self) -> None:
    method by_tag (line 51) | def by_tag(self, access: 'Access') -> Dict[str, List[Dict[str, Any]]]:
  class OperationError (line 110) | class OperationError(Exception):
  class Access (line 114) | class Access:
    method __init__ (line 116) | def __init__(self) -> None:
    method new (line 120) | def new(self, path: Path) -> None:
    method open (line 125) | def open(self, path: Path) -> None:
    method close (line 129) | def close(self) -> None:
    method sync (line 135) | def sync(self) -> None:
    method create_blog (line 139) | def create_blog(self, blog: Blog) -> Blog:
    method retrieve_blog (line 146) | def retrieve_blog(self, key: str) -> Blog:
    method create_post (line 149) | def create_post(self, blog: Blog, post: Post) -> Post:
    method retrieve_post (line 157) | def retrieve_post(self, key: str) -> Post:
    method update_post (line 160) | def update_post(self, post: Post) -> Post:
    method delete_post (line 164) | def delete_post(self, post: Post) -> None:
    method __iter__ (line 167) | def __iter__(self) -> Iterator[Union[Blog, Post]]:
    method blog_iter (line 174) | def blog_iter(self) -> Iterator[Blog]:
    method post_iter (line 179) | def post_iter(self, blog: Blog) -> Iterator[Post]:
    method post_title_iter (line 185) | def post_title_iter(self, blog: Blog, title: str) -> Iterator[Post]:
    method blog_title_iter (line 188) | def blog_title_iter(self, title: str) -> Iterator[Blog]:
  function database_script (line 195) | def database_script(access: Access) -> None:
  class Render (line 237) | class Render:
    method __init__ (line 239) | def __init__(self, access: Access) -> None:
    method emit_all (line 242) | def emit_all(self, destination: TextIO=sys.stdout) -> None:
    method emit_blog (line 247) | def emit_blog(self, blog: Blog, output: TextIO) -> None:
    method emit_post (line 257) | def emit_post(self, post: Post) -> None:
    method emit_index (line 272) | def emit_index(self) -> None:
  class Access2 (line 302) | class Access2(Access):
    method create_post (line 304) | def create_post(self, blog: Blog, post: Post) -> Post:
    method delete_post (line 312) | def delete_post(self, post: Post) -> None:
    method post_iter (line 320) | def post_iter(self, blog: Blog) -> Iterator[Post]:
  class Access3 (line 355) | class Access3(Access2):
    method new (line 357) | def new(self, path: Path) -> None:
    method create_blog (line 361) | def create_blog(self, blog: Blog) -> Blog:
    method blog_iter (line 366) | def blog_iter(self) -> Iterator[Blog]:
  class Access4 (line 398) | class Access4(Access3):
    method new (line 400) | def new(self, path: Path) -> None:
    method create_blog (line 404) | def create_blog(self, blog):
    method update_blog (line 412) | def update_blog(self, blog: Blog) -> Blog:
    method blog_iter (line 431) | def blog_iter(self) -> Iterator[Blog]:
    method blog_title_iter (line 434) | def blog_title_iter(self, title: str) -> Iterator[Blog]:
  function create (line 470) | def create(access, blogs=100, posts_per_blog=100) -> None:
  function performance (line 484) | def performance(cycles=3):

FILE: Chapter_12/ch12_ex2.py
  function adapt_currency (line 18) | def adapt_currency(value):
  function convert_currency (line 25) | def convert_currency(bytes):

FILE: Chapter_12/ch12_ex3.py
  class Blog (line 24) | class Blog:
    method __post_init__ (line 33) | def __post_init__(self) -> None:
    method entries (line 37) | def entries(self) -> List['Post']:
    method by_tag (line 43) | def by_tag(self) -> Dict[str, List[Dict[str, Any]]]:
  class Post (line 49) | class Post:
    method append (line 57) | def append(self, tag):
  class Access (line 62) | class Access:
    method open (line 67) | def open(self, path: Path) -> None:
    method get_blog (line 71) | def get_blog(self, id: str) -> Blog:
    method add_blog (line 81) | def add_blog(self, blog: Blog) -> Blog:
    method get_post (line 91) | def get_post(self, id: str) -> Post:
    method add_post (line 111) | def add_post(self, blog: Blog, post: Post) -> Post:
    method blog_iter (line 155) | def blog_iter(self) -> Iterator[Blog]:
    method post_iter (line 166) | def post_iter(self, blog: Blog) -> Iterator[Post]:
    method post_by_tag (line 174) | def post_by_tag(self, blog: Blog) -> Dict[str, List[Dict[str, Any]]]:

FILE: Chapter_12/ch12_ex4.py
  class Blog (line 63) | class Blog(Base):
    method as_dict (line 68) | def as_dict(self):
  class Post (line 84) | class Post(Base):
    method as_dict (line 94) | def as_dict(self):
  class Tag (line 104) | class Tag(Base):

FILE: Chapter_13/ch13_e1_ex2.py
  class Wheel2 (line 42) | class Wheel2(Wheel):
    method __call__ (line 44) | def __call__(
  class American2 (line 54) | class American2(DoubleZero, Wheel2):
  class European2 (line 58) | class European2(Zero, Wheel2):
  class Wheel3 (line 76) | class Wheel3:
    method __init__ (line 78) | def __init__(self, seed: Optional[int] = None) -> None:
    method __call__ (line 82) | def __call__(
  function roulette_server_3 (line 104) | def roulette_server_3(count: int = 1) -> None:
  function server_3 (line 119) | def server_3() -> None:

FILE: Chapter_13/ch13_e1_ex3.py
  class Wheel2 (line 42) | class Wheel2(Wheel):
    method __call__ (line 44) | def __call__(
  class American2 (line 54) | class American2(DoubleZero, Wheel2):
  class European2 (line 58) | class European2(Zero, Wheel2):
  class Wheel3 (line 66) | class Wheel3:
    method __init__ (line 68) | def __init__(self) -> None:
    method __call__ (line 72) | def __call__(
  class Table (line 102) | class Table:
    method __init__ (line 104) | def __init__(self, stake: int = 100) -> None:
    method place_bet (line 108) | def place_bet(self, name: str, amount: int) -> None:
    method clear_bets (line 111) | def clear_bets(self, name: str) -> None:
    method resolve (line 114) | def resolve(self, spin: Dict[str, Tuple[int, int]]) -> List[Tuple[str,...
  class WSGI (line 130) | class WSGI:
    method __call__ (line 132) | def __call__(
  class RESTException (line 138) | class RESTException(Exception):
  class Roulette (line 142) | class Roulette(WSGI):
    method __init__ (line 144) | def __init__(self, wheel: Wheel) -> None:
    method __call__ (line 149) | def __call__(
    method player_app (line 172) | def player_app(
    method bet_app (line 187) | def bet_app(
    method wheel_app (line 215) | def wheel_app(
  function roulette_server_3 (line 258) | def roulette_server_3(count: int = 1) -> None:
  function roulette_client (line 278) | def roulette_client(
  function server_3 (line 299) | def server_3() -> None:

FILE: Chapter_13/ch13_e1_ex4.py
  class Wheel2 (line 42) | class Wheel2(Wheel):
    method __call__ (line 44) | def __call__(
  class American2 (line 54) | class American2(DoubleZero, Wheel2):
  class European2 (line 58) | class European2(Zero, Wheel2):
  class Wheel3 (line 66) | class Wheel3:
    method __init__ (line 68) | def __init__(self):
    method __call__ (line 72) | def __call__(
  class Table (line 102) | class Table:
    method __init__ (line 104) | def __init__(self, stake: int = 100) -> None:
    method place_bet (line 108) | def place_bet(self, name: str, amount: int) -> None:
    method clear_bets (line 111) | def clear_bets(self, name: str) -> None:
    method resolve (line 114) | def resolve(self, spin: Dict[str, Tuple[int, int]]) -> List[Tuple[str,...
  class WSGI (line 130) | class WSGI:
    method __call__ (line 132) | def __call__(
  class RESTException (line 138) | class RESTException(Exception):
  class Roulette (line 142) | class Roulette(WSGI):
    method __init__ (line 144) | def __init__(self, wheel):
    method __call__ (line 149) | def __call__(
    method player_app (line 172) | def player_app(
    method bet_app (line 187) | def bet_app(
    method wheel_app (line 215) | def wheel_app(
  function roulette_server_4 (line 258) | def roulette_server_4(count: int = 1):
  function roulette_client (line 278) | def roulette_client(method="GET", path="/", data=None):
  class Authentication (line 305) | class Authentication:
    method __init__ (line 308) | def __init__(self, username: bytes, password: bytes, salt: Optional[by...
    method _iter_hash (line 315) | def _iter_hash(iterations: int, salt: bytes, username: bytes, password...
    method __eq__ (line 321) | def __eq__(self, other: Any) -> bool:
    method __hash__ (line 325) | def __hash__(self) -> int:
    method __repr__ (line 328) | def __repr__(self) -> str:
    method match (line 333) | def match(self, password: bytes) -> bool:
  class Users (line 339) | class Users(dict):
    method __init__ (line 341) | def __init__(self, *args, **kw) -> None:
    method add (line 346) | def add(self, authentication: Authentication) -> None:
    method match (line 351) | def match(self, username: bytes, password: bytes) -> bool:
  class Authenticate (line 384) | class Authenticate(WSGI):
    method __init__ (line 386) | def __init__(self, users, target_app):
    method __call__ (line 390) | def __call__(
  class Some_App (line 410) | class Some_App(WSGI):
    method __call__ (line 412) | def __call__(
  function authenticated_client (line 425) | def authenticated_client(
  function auth_server (line 454) | def auth_server(count: int = 1) -> None:
  function server_5 (line 470) | def server_5() -> None:

FILE: Chapter_13/ch13_ex1.py
  class Wheel (line 55) | class Wheel:
    method __init__ (line 58) | def __init__(self, seed: Optional[int] = None) -> None:
    method zero (line 73) | def zero(self) -> List[Dict[str, Tuple[int, int]]]:
    method redblack (line 77) | def redblack(n: int) -> str:
    method hilo (line 83) | def hilo(n: int) -> str:
    method evenodd (line 87) | def evenodd(n: int) -> str:
    method spin (line 90) | def spin(self) -> Dict[str, Tuple[int, int]]:
  class Zero (line 94) | class Zero:
    method zero (line 96) | def zero(self) -> List[Dict[str, Tuple[int, int]]]:
  class DoubleZero (line 100) | class DoubleZero(Zero):
    method zero (line 102) | def zero(self) -> List[Dict[str, Tuple[int, int]]]:
  class American (line 108) | class American(DoubleZero, Wheel):
  class European (line 112) | class European(Zero, Wheel):
  function wsgi_wheel (line 155) | def wsgi_wheel(
  function roulette_server (line 176) | def roulette_server(count: int = 1) -> None:
  class Response (line 194) | class Response(NamedTuple):
    method __str__ (line 199) | def __str__(self) -> str:
  function json_get (line 203) | def json_get(path: str = "/") -> Response:
  function server (line 226) | def server() -> None:

FILE: Chapter_13/ch13_ex2.py
  class Domino (line 21) | class Domino:
    method double (line 26) | def double(self):
    method __repr__ (line 29) | def __repr__(self):
  class Boneyard (line 36) | class Boneyard:
    method __init__ (line 47) | def __init__(self, limit=6):
    method deal (line 53) | def deal(self, tiles: int = 7, hands: int = 4) -> List[List[Tuple[int,...
  function dominoes (line 73) | def dominoes(n: str) -> Tuple[Dict[str, Any], int]:
  function hands (line 89) | def hands(h: int, c: int) -> Tuple[Dict[str, Any], int]:
  function openapi (line 121) | def openapi() -> Dict[str, Any]:

FILE: Chapter_13/ch13_ex3.py
  class Status (line 21) | class Status(str, Enum):
  class Dice (line 27) | class Dice:
    method reroll (line 32) | def reroll(self, keep_positions: List[int]) -> None:
  function make_dice (line 39) | def make_dice(n_dice: int) -> Dice:
  function openapi (line 89) | def openapi() -> Dict[str, Any]:
  function make_roll (line 95) | def make_roll() -> Tuple[Dict[str, Any], HTTPStatus, Dict[str, str]]:
  function get_roll (line 113) | def get_roll(identifier) -> Tuple[Dict[str, Any], HTTPStatus]:
  function patch_roll (line 121) | def patch_roll(identifier) -> Tuple[Dict[str, Any], HTTPStatus]:
  class BadRequest (line 138) | class BadRequest(Exception):
  function make_app (line 142) | def make_app() -> Flask:

FILE: Chapter_13/ch13_ex4.py
  function demo (line 17) | def demo(headers=None):
  function mock_requests (line 54) | def mock_requests(monkeypatch):
  function test_demo (line 75) | def test_demo(mock_requests):

FILE: Chapter_13/ch13_ex5.py
  function init_app (line 56) | def init_app(app):
  function valid_api_key (line 66) | def valid_api_key(view_function: Callable) -> Callable:
  function api_key_in (line 79) | def api_key_in(valid_values: Set[str]):
  function openapi (line 102) | def openapi() -> Dict[str, Any]:
  function create_roll (line 109) | def create_roll() -> Tuple[Any, HTTPStatus, Dict[str, Any]]:
  function get_roll (line 131) | def get_roll(identifier) -> Tuple[Dict[str, Any], HTTPStatus]:
  function patch_roll (line 142) | def patch_roll(identifier) -> Tuple[Dict[str, Any], HTTPStatus]:
  class BadRequest (line 164) | class BadRequest(Exception):
  function make_app (line 168) | def make_app() -> Flask:

FILE: Chapter_13/ch13_ex6.py
  class Simulation (line 21) | class Simulation(multiprocessing.Process):
    method __init__ (line 23) | def __init__(
    method run (line 32) | def run(self) -> None:
  class Summarize (line 46) | class Summarize(multiprocessing.Process):
    method __init__ (line 48) | def __init__(self, queue: multiprocessing.SimpleQueue) -> None:
    method run (line 52) | def run(self) -> None:
  function server_6 (line 68) | def server_6() -> None:

FILE: Chapter_13/simulation_model.py
  class DealerRule (line 17) | class DealerRule:
  class Hit17 (line 21) | class Hit17(DealerRule):
  class Stand17 (line 26) | class Stand17(DealerRule):
  class SplitRule (line 31) | class SplitRule:
  class ReSplit (line 35) | class ReSplit(SplitRule):
  class NoReSplit (line 40) | class NoReSplit(SplitRule):
  class NoReSplitAces (line 45) | class NoReSplitAces(SplitRule):
  class Table (line 50) | class Table:
    method __init__ (line 52) | def __init__(self, decks: int, limit: int, dealer: DealerRule, split: ...
    method as_tuple (line 59) | def as_tuple(self):
  class PlayerStrategy (line 69) | class PlayerStrategy:
  class SomeStrategy (line 73) | class SomeStrategy(PlayerStrategy):
  class AnotherStrategy (line 77) | class AnotherStrategy(PlayerStrategy):
  class BettingStrategy (line 81) | class BettingStrategy:
    method bet (line 83) | def bet(self) -> int:
    method record_win (line 86) | def record_win(self) -> None:
    method record_loss (line 89) | def record_loss(self) -> None:
  class Flat (line 93) | class Flat(BettingStrategy):
  class Martingale (line 97) | class Martingale(BettingStrategy):
  class OneThreeTwoSix (line 101) | class OneThreeTwoSix(BettingStrategy):
  class Player (line 105) | class Player:
    method __init__ (line 107) | def __init__(self, play: PlayerStrategy, betting: BettingStrategy, rou...
    method reset (line 113) | def reset(self) -> None:
    method as_tuple (line 117) | def as_tuple(self) -> Tuple:
  class Simulate (line 132) | class Simulate:
    method __init__ (line 134) | def __init__(
    method __iter__ (line 145) | def __iter__(self) -> Iterator[Tuple]:

FILE: Chapter_14/ch14_ex1.py
  function simulate_blackjack (line 19) | def simulate_blackjack() -> None:
  function location_list (line 50) | def location_list(config_name: str = "someapp.config") -> List[Path]:
  function main_ini (line 119) | def main_ini(config: configparser.ConfigParser) -> None:

FILE: Chapter_14/ch14_ex2.py
  function simulate (line 27) | def simulate(table: Table, player: Player, outputpath: Path, samples: in...
  function simulate_SomeStrategy_Flat (line 38) | def simulate_SomeStrategy_Flat() -> None:
  class AppConfig (line 63) | class AppConfig:
  function simulate_c (line 74) | def simulate_c(config: Union[Type[AppConfig], SimpleNamespace]) -> None:
  class Example2 (line 82) | class Example2(AppConfig):
  function make_config (line 166) | def make_config(

FILE: Chapter_14/ch14_ex3.py
  function simulate (line 21) | def simulate(table: Table, player: Player, outputpath: Path, samples: in...
  class AttrChainMap (line 172) | class AttrChainMap(ChainMap):
    method __getattr__ (line 174) | def __getattr__(self, name: str) -> Any:
    method __setattr__ (line 179) | def __setattr__(self, name: str, value: Any) -> None:

FILE: Chapter_14/ch14_ex4.py
  function main_nested_dict (line 54) | def main_nested_dict(config: Dict[str, Any]) -> None:
  function simulate (line 133) | def simulate(table: Table, player: Player, outputpath: Path, samples: in...
  function main_cm (line 141) | def main_cm(config: Dict[str, Any]) -> None:

FILE: Chapter_14/ch14_ex5.py
  class PropertyParser (line 74) | class PropertyParser:
    method read_string (line 76) | def read_string(self, data: str) -> Iterator[Tuple[str, str]]:
    method read_file (line 79) | def read_file(self, file: IO[str]) -> Iterator[Tuple[str, str]]:
    method read (line 83) | def read(self, path: Path) -> Iterator[Tuple[str, str]]:
    method _parse (line 89) | def _parse(self, data: str) -> Iterator[Tuple[str, str]]:
    method load (line 109) | def load(
    method loads (line 119) | def loads(self, data: str) -> Iterator[Tuple[str, str]]:
    method _escape (line 122) | def _escape(self, data: str) -> str:
    method _escape2 (line 127) | def _escape2(self, data: str) -> str:
  function main_cm_prop (line 186) | def main_cm_prop(config):

FILE: Chapter_14/ch14_ex6.py
  class Configuration (line 122) | class Configuration:
    method read_file (line 124) | def read_file(self, file):
    method read (line 127) | def read(self, filename):
    method read_string (line 130) | def read_string(self, text):
    method get (line 133) | def get(self, qual_name, default):
    method __getitem__ (line 141) | def __getitem__(self, section):
  function main_cm_prop (line 146) | def main_cm_prop(config):

FILE: Chapter_14/simulation_model.py
  class DealerRule (line 20) | class DealerRule:
    method __repr__ (line 22) | def __repr__(self) -> str:
  class Hit17 (line 25) | class Hit17(DealerRule):
  class Stand17 (line 30) | class Stand17(DealerRule):
  class SplitRule (line 35) | class SplitRule:
    method __repr__ (line 37) | def __repr__(self) -> str:
  class ReSplit (line 41) | class ReSplit(SplitRule):
  class NoReSplit (line 46) | class NoReSplit(SplitRule):
  class NoReSplitAces (line 51) | class NoReSplitAces(SplitRule):
  class Table (line 57) | class Table:
  class PlayerStrategy (line 66) | class PlayerStrategy:
    method __repr__ (line 68) | def __repr__(self) -> str:
  class SomeStrategy (line 72) | class SomeStrategy(PlayerStrategy):
  class AnotherStrategy (line 76) | class AnotherStrategy(PlayerStrategy):
  class BettingStrategy (line 80) | class BettingStrategy:
    method __repr__ (line 82) | def __repr__(self) -> str:
    method bet (line 85) | def bet(self) -> int:
    method record_win (line 88) | def record_win(self) -> None:
    method record_loss (line 91) | def record_loss(self) -> None:
  class Flat (line 95) | class Flat(BettingStrategy):
  class Martingale (line 99) | class Martingale(BettingStrategy):
  class OneThreeTwoSix (line 103) | class OneThreeTwoSix(BettingStrategy):
  class Player (line 108) | class Player:
    method __post_init__ (line 118) | def __post_init__(self):
    method reset (line 121) | def reset(self) -> None:
  class Simulate (line 131) | class Simulate:
    method __iter__ (line 138) | def __iter__(self) -> Iterator[Tuple]:
  function check (line 162) | def check(path: Path) -> None:

FILE: Chapter_15/ch15_ex1.py
  class DominoBoneYard (line 15) | class DominoBoneYard:
    method __init__ (line 46) | def __init__(self, limit: int = 6) -> None:
    method double (line 50) | def double(self, domino: Tuple[int, int]) -> bool:
    method score (line 54) | def score(self, domino: Tuple[int, int]) -> int:
    method hand_iter (line 57) | def hand_iter(self, players: int = 4) -> Iterator[List[Tuple[int, int]]]:
    method can_play_first (line 61) | def can_play_first(self, hand: List[Tuple[int, int]]) -> bool:
    method score_hand (line 67) | def score_hand(self, hand: List[Tuple[int, int]]) -> int:
    method rank_hand (line 70) | def rank_hand(self, hand: List[Tuple[int, int]]) -> None:
    method doubles_indices (line 73) | def doubles_indices(self, hand: List[Tuple[int, int]]) -> List[int]:
  class Domino (line 81) | class Domino(NamedTuple):
    method double (line 85) | def double(self) -> bool:
    method score (line 88) | def score(self) -> int:
  class Hand (line 92) | class Hand(list):
    method score (line 94) | def score(self) -> int:
    method rank (line 97) | def rank(self) -> None:
    method doubles_indices (line 100) | def doubles_indices(self) -> List[int]:
  class DominoBoneYard2 (line 104) | class DominoBoneYard2:
    method __init__ (line 106) | def __init__(self, limit: int = 6) -> None:
    method hand_iter (line 110) | def hand_iter(self, players: int = 4) -> Iterator[Hand]:
  class Hand3 (line 152) | class Hand3(Hand):
    method highest_double_index (line 154) | def highest_double_index(self) -> Optional[int]:
  class DominoBoneYard3 (line 165) | class DominoBoneYard3(DominoBoneYard2):
    method hand_iter (line 167) | def hand_iter(self, players: int = 4) -> Iterator[Hand3]:
  class FancyDealer4 (line 193) | class FancyDealer4:
    method __init__ (line 195) | def __init__(self):
    method hand_iter (line 198) | def hand_iter(
  class DominoBoneYard3b (line 224) | class DominoBoneYard3b:
    method __init__ (line 228) | def __init__(self, limit: int = 6) -> None:
    method hand_iter (line 232) | def hand_iter(self, players: int = 4) -> Iterator[Hand3]:
  class DominoBoneYard3c (line 259) | class DominoBoneYard3c:
    method __init__ (line 267) | def __init__(self, limit: int = 6) -> None:
    method hand_iter (line 273) | def hand_iter(self, players: int = 4) -> Iterator[Hand]:
  class Hand4 (line 280) | class Hand4(Hand3):
    method __init__ (line 282) | def __init__(self, *args) -> None:
    method doubles_indices (line 287) | def doubles_indices(self) -> List[int]:

FILE: Chapter_15/ch15_ex2.py
  class Domino_1 (line 29) | class Domino_1(NamedTuple):
    method double (line 34) | def double(self) -> bool:
    method score (line 38) | def score(self) -> int:
  class Domino_2 (line 46) | class Domino_2:
    method double (line 51) | def double(self) -> bool:
    method score (line 55) | def score(self) -> int:
  function builder (line 62) | def builder(v1: int, v2: int) -> Domino:
  class Hand (line 89) | class Hand(list):
    method __init__ (line 91) | def __init__(self, *args: Domino) -> None:
    method score (line 94) | def score(self) -> int:
    method rank (line 97) | def rank(self) -> None:
    method doubles (line 100) | def doubles(self) -> List[Domino_1]:
    method highest_double (line 103) | def highest_double(self) -> Optional[Domino_1]:
  class DominoBoneYard (line 110) | class DominoBoneYard:
    method __init__ (line 118) | def __init__(self, limit: int = 6) -> None:
    method draw (line 124) | def draw(self, n: int = 1) -> Optional[List[Domino]]:
    method hand_iter (line 131) | def hand_iter(self, players: int = 4) -> Iterator[Hand]:
  class Hand_X1 (line 175) | class Hand_X1(Hand):
    method __init__ (line 177) | def __init__(self, *args) -> None:
    method matches (line 184) | def matches(self, spots: int) -> List[Domino_1]:

FILE: Chapter_16/ch16_ex1.py
  class Player (line 16) | class Player:
    method __init__ (line 18) | def __init__(self, bet: str, strategy: str, stake: int) -> None:
  function logged (line 31) | def logged(cls: Type) -> Type:
  class Player_2 (line 49) | class Player_2:
    method __init__ (line 52) | def __init__(self, bet: str, strategy: str, stake: int) -> None:
  class LoggedClassMeta (line 60) | class LoggedClassMeta(type):
    method __new__ (line 62) | def __new__(cls, name, bases, namespace, **kwds):
  class LoggedClass (line 68) | class LoggedClass(metaclass=LoggedClassMeta):
  class Player_3 (line 75) | class Player_3(LoggedClass):
    method __init__ (line 77) | def __init__(self, bet: str, strategy: str, stake: int) -> None:

FILE: Chapter_16/ch16_ex10.py
  class WaitQueueHandler (line 22) | class WaitQueueHandler(logging.handlers.QueueHandler):
    method enqueue (line 24) | def enqueue(self, record):
  class Log_Producer_2 (line 31) | class Log_Producer_2(Log_Producer):

FILE: Chapter_16/ch16_ex2.py
  function log_to (line 21) | def log_to(*names: str):
  class Player (line 39) | class Player:
    method __init__ (line 40) | def __init__(self, bet: str, strategy: str, stake: int) -> None:
  class Table (line 47) | class Table:
    method add_player (line 48) | def add_player(self, player: Player) -> None:

FILE: Chapter_16/ch16_ex4.py
  class Main (line 23) | class Main(LoggedClass):
    method __init__ (line 25) | def __init__(self) -> None:
    method run (line 28) | def run(self) -> int:
  function demo4a (line 81) | def demo4a() -> None:
  function demo4b (line 102) | def demo4b() -> None:

FILE: Chapter_16/ch16_ex5.py
  class BettingStrategy (line 62) | class BettingStrategy(LoggedClass):
    method bet (line 64) | def bet(self) -> int:
    method record_win (line 67) | def record_win(self) -> None:
    method record_loss (line 70) | def record_loss(self) -> None:
  class OneThreeTwoSix (line 74) | class OneThreeTwoSix(BettingStrategy):
    method __init__ (line 76) | def __init__(self) -> None:
    method _state (line 79) | def _state(self) -> Dict[str, int]:
    method bet (line 82) | def bet(self) -> int:
    method record_win (line 87) | def record_win(self) -> None:
    method record_loss (line 91) | def record_loss(self) -> None:
  function audited (line 99) | def audited(cls: Type) -> Type:
  class AuditedClassMeta (line 110) | class AuditedClassMeta(LoggedClassMeta):
    method __new__ (line 112) | def __new__(cls, name, bases, namespace, **kwds):
  class AuditedClass (line 122) | class AuditedClass(LoggedClass, metaclass=AuditedClassMeta):
  class Table (line 127) | class Table(AuditedClass):
    method bet (line 129) | def bet(self, bet: str, amount: int) -> None:

FILE: Chapter_16/ch16_ex6.py
  class UserLogRecordFactory (line 56) | class UserLogRecordFactory:
    method __init__ (line 57) | def __init__(self) -> None:
    method __call__ (line 61) | def __call__(self, *args, **kwargs) -> logging.LogRecord:
  class UserLogAdapter (line 73) | class UserLogAdapter(logging.LoggerAdapter):
    method process (line 74) | def process(self, msg, kwargs):

FILE: Chapter_16/ch16_ex7.py
  class Player (line 19) | class Player:
    method bet (line 22) | def bet(self) -> None:

FILE: Chapter_16/ch16_ex8.py
  class TailHandler (line 24) | class TailHandler(logging.handlers.MemoryHandler):
    method shouldFlush (line 25) | def shouldFlush(self, record: logging.LogRecord) -> bool:

FILE: Chapter_16/ch16_ex9.py
  class Log_Consumer_1 (line 49) | class Log_Consumer_1(multiprocessing.Process):
    method __init__ (line 52) | def __init__(self, queue):
    method run (line 60) | def run(self):
  class Log_Producer (line 73) | class Log_Producer(multiprocessing.Process):
    method __init__ (line 76) | def __init__(self, proc_id, queue):
    method run (line 85) | def run(self):
  function demo (line 92) | def demo():

FILE: Chapter_17/ch17_ex1.py
  class Suit (line 17) | class Suit(enum.Enum):
  class Card (line 24) | class Card:
    method __init__ (line 26) | def __init__(
    method __str__ (line 34) | def __str__(self) -> str:
  class AceCard (line 38) | class AceCard(Card):
    method __init__ (line 40) | def __init__(self, rank: int, suit: Suit) -> None:
  class FaceCard (line 44) | class FaceCard(Card):
    method __init__ (line 46) | def __init__(self, rank: int, suit: Suit) -> None:
  class LogicError (line 50) | class LogicError(Exception):
  function card (line 54) | def card(rank: int, suit: Suit) -> Card:
  class Deck1 (line 68) | class Deck1(list):
    method __init__ (line 70) | def __init__(self, size: int = 1) -> None:
  class Deck2 (line 84) | class Deck2(list):
    method __init__ (line 86) | def __init__(
  class TestCard (line 115) | class TestCard(unittest.TestCase):
    method setUp (line 117) | def setUp(self) -> None:
    method test_should_returnStr (line 120) | def test_should_returnStr(self) -> None:
    method test_should_getAttrValues (line 123) | def test_should_getAttrValues(self) -> None:
  class TestAceCard (line 130) | class TestAceCard(unittest.TestCase):
    method setUp (line 132) | def setUp(self) -> None:
    method test_should_returnStr (line 136) | def test_should_returnStr(self) -> None:
    method test_should_getAttrValues (line 139) | def test_should_getAttrValues(self) -> None:
  class TestFaceCard (line 146) | class TestFaceCard(unittest.TestCase):
    method setUp (line 148) | def setUp(self) -> None:
    method test_should_returnStr (line 152) | def test_should_returnStr(self) -> None:
    method test_should_getAttrValues (line 155) | def test_should_getAttrValues(self) -> None:
  function suite2 (line 165) | def suite2() -> unittest.TestSuite:
  class TestCardFactory (line 185) | class TestCardFactory(unittest.TestCase):
    method test_rank1_should_createAceCard (line 187) | def test_rank1_should_createAceCard(self) -> None:
    method test_rank2_should_createCard (line 191) | def test_rank2_should_createCard(self) -> None:
    method test_rank10_should_createCard (line 195) | def test_rank10_should_createCard(self) -> None:
    method test_rank10_should_createFaceCard (line 199) | def test_rank10_should_createFaceCard(self) -> None:
    method test_rank13_should_createFaceCard (line 203) | def test_rank13_should_createFaceCard(self) -> None:
    method test_otherRank_should_exception (line 207) | def test_otherRank_should_exception(self) -> None:
  function suite3 (line 217) | def suite3() -> unittest.TestSuite:
  class DeckEmpty (line 233) | class DeckEmpty(Exception):
  class Deck3 (line 237) | class Deck3(list):
    method __init__ (line 239) | def __init__(
    method deal (line 253) | def deal(self) -> Card:
  class TestDeckBuild (line 266) | class TestDeckBuild(unittest.TestCase):
    method setUp (line 268) | def setUp(self) -> None:
    method test_Deck3_should_build (line 273) | def test_Deck3_should_build(self) -> None:
  class TestDeckDeal (line 286) | class TestDeckDeal(unittest.TestCase):
    method setUp (line 288) | def setUp(self) -> None:
    method test_Deck3_should_deal (line 294) | def test_Deck3_should_deal(self) -> None:
    method test_empty_deck_should_exception (line 302) | def test_empty_deck_should_exception(self) -> None:
  function suite4 (line 312) | def suite4():
  function ackermann (line 329) | def ackermann(m: int, n: int) -> int:
  class GameStat (line 388) | class GameStat(NamedTuple):
  function gamestat_iter (line 400) | def gamestat_iter(source: Iterable[Dict[str, str]]) -> Iterator[GameStat]:
  function rounds_final (line 405) | def rounds_final(path: Path) -> DefaultDict[int, List[int]]:
  class Test_Missing (line 429) | class Test_Missing(unittest.TestCase):
    method setUp (line 431) | def setUp(self) -> None:
    method test_missingFile_should_returnDefault (line 439) | def test_missingFile_should_returnDefault(self) -> None:
  class Test_Damaged (line 445) | class Test_Damaged(unittest.TestCase):
    method setUp (line 447) | def setUp(self) -> None:
    method test_damagedFile_should_raiseException (line 452) | def test_damagedFile_should_raiseException(self) -> None:
  function suite7 (line 458) | def suite7():
  function float_or_none (line 491) | def float_or_none(text: str) -> Optional[float]:
  class Test_RTD (line 500) | class Test_RTD(unittest.TestCase):
    method runTest (line 502) | def runTest(self) -> None:
    method example (line 508) | def example(
  function suite9 (line 539) | def suite9():
  class Test_Performance (line 559) | class Test_Performance(unittest.TestCase):
    method test_simpleCalc_shouldbe_fastEnough (line 561) | def test_simpleCalc_shouldbe_fastEnough(self):
  function suite10 (line 573) | def suite10():

FILE: Chapter_17/ch17_ex2.py
  function build_test_db (line 32) | def build_test_db(name="sqlite:///./data/ch17_blog.db"):
  class Test_Blog_Queries (line 47) | class Test_Blog_Queries(unittest.TestCase):
    method setUpClass (line 53) | def setUpClass() -> None:
    method setUp (line 90) | def setUp(self) -> None:
    method test_query_eqTitle_should_return1Blog (line 93) | def test_query_eqTitle_should_return1Blog(self) -> None:
    method test_query_likeTitle_should_return2Blog (line 99) | def test_query_likeTitle_should_return2Blog(self) -> None:
    method test_query_eqW42_tag_should_return2Post (line 104) | def test_query_eqW42_tag_should_return2Post(self) -> None:
    method test_query_eqICW_tag_should_return1Post (line 110) | def test_query_eqICW_tag_should_return1Post(self) -> None:
  function suite8 (line 127) | def suite8() -> unittest.TestSuite:

FILE: Chapter_17/test_ch17.py
  class Suit (line 22) | class Suit(enum.Enum):
  class Card (line 29) | class Card:
    method __init__ (line 31) | def __init__(
    method __str__ (line 39) | def __str__(self) -> str:
  class AceCard (line 43) | class AceCard(Card):
    method __init__ (line 45) | def __init__(self, rank: int, suit: Suit) -> None:
  class FaceCard (line 49) | class FaceCard(Card):
    method __init__ (line 51) | def __init__(self, rank: int, suit: Suit) -> None:
  class LogicError (line 55) | class LogicError(Exception):
  function card (line 59) | def card(rank: int, suit: Suit) -> Card:
  class Deck1 (line 70) | class Deck1(list):
    method __init__ (line 72) | def __init__(self, size: int = 1) -> None:
  class Deck2 (line 86) | class Deck2(list):
    method __init__ (line 88) | def __init__(
  function test_card (line 116) | def test_card():
  function test_ace_card (line 125) | def test_ace_card():
  function test_face_card (line 134) | def test_face_card():
  function test_card_factory (line 153) | def test_card_factory():
  class DeckEmpty (line 183) | class DeckEmpty(Exception):
  class Deck3 (line 187) | class Deck3(list):
    method __init__ (line 189) | def __init__(self, size=1, random=random.Random(), card_factory=card):
    method deal (line 196) | def deal(self):
  function deck_context (line 210) | def deck_context():
  function test_deck_build (line 223) | def test_deck_build(deck_context):
  function test_deck_deal (line 237) | def test_deck_deal(deck_context):
  function ackermann (line 258) | def ackermann(m, n):
  class GameStat (line 292) | class GameStat(NamedTuple):
  function gamestat_iter (line 302) | def gamestat_iter(iterator):
  function rounds_final (line 307) | def rounds_final(path: Path):
  function no_file_path (line 331) | def no_file_path():
  function test_missing (line 341) | def test_missing(no_file_path):
  function damaged_file_path (line 347) | def damaged_file_path():
  function test_damaged (line 355) | def test_damaged(damaged_file_path):
  function built_test_db (line 379) | def built_test_db(name="sqlite:///./data/ch17_blog.db"):
  function db_session_maker (line 390) | def db_session_maker():
  function test_database (line 428) | def test_database(db_session_maker):
  function float_or_none (line 465) | def float_or_none(text):
  function rtd_example (line 479) | def rtd_example(request):
  function test_rtd (line 483) | def test_rtd(rtd_example):
  function test_performance (line 513) | def test_performance():

FILE: Chapter_18/ch18_ex1.py
  function get_options_1 (line 23) | def get_options_1(
  function test_get_config_1 (line 151) | def test_get_config_1(capsys):

FILE: Chapter_18/ch18_ex2.py
  function nint (line 28) | def nint(x: Optional[str]) -> Optional[int]:
  function get_options_2 (line 34) | def get_options_2(argv: List[str] = sys.argv[1:]) -> argparse.Namespace:

FILE: Chapter_18/ch18_ex3.py
  function simulate_blackjack (line 36) | def simulate_blackjack(config: argparse.Namespace) -> None:
  class Build_Config (line 90) | class Build_Config:
    method __init__ (line 92) | def __init__(self, argv: List[str]) -> None:
    method __enter__ (line 95) | def __enter__(self) -> argparse.Namespace:
    method __exit__ (line 98) | def __exit__(self, *exc) -> None:
  class Setup_Logging (line 121) | class Setup_Logging:
    method __init__ (line 123) | def __init__(self, stream=sys.stderr, disable_existing_loggers=False) ...
    method __enter__ (line 143) | def __enter__(self) -> "Setup_Logging":
    method __exit__ (line 147) | def __exit__(self, *exc) -> None:
  class ClassLogger (line 157) | class ClassLogger:
    method work (line 160) | def work(self) -> None:
  class InstanceLogger (line 165) | class InstanceLogger:
    method __init__ (line 167) | def __init__(self, name: str) -> None:
    method work (line 170) | def work(self) -> None:
  function simulate_blackjack_betting (line 219) | def simulate_blackjack_betting(config: argparse.Namespace) -> None:
  class Command (line 248) | class Command:
    method __init__ (line 257) | def __init__(self) -> None:
    method configure (line 260) | def configure(self, namespace: argparse.Namespace) -> None:
    method run (line 263) | def run(self) -> None:
  class Simulate_Command (line 268) | class Simulate_Command(Command):
    method run (line 278) | def run(self) -> None:
  class Analyze_Command (line 320) | class Analyze_Command(Command):
    method run (line 322) | def run(self) -> None:
  class Command_Sequence (line 343) | class Command_Sequence(Command):
    method __init__ (line 349) | def __init__(self) -> None:
    method configure (line 352) | def configure(self, config: argparse.Namespace) -> None:
    method run (line 356) | def run(self) -> None:
  class Simulate_and_Analyze (line 361) | class Simulate_and_Analyze(Command_Sequence):
  class ForAllBets_Simulate (line 378) | class ForAllBets_Simulate(Command):
    method run (line 380) | def run(self) -> None:

FILE: Chapter_19/some_algorithm/abstraction.py
  class AbstractSomeAlgorithm (line 11) | class AbstractSomeAlgorithm:

FILE: Chapter_19/some_algorithm/long_version.py
  class Implementation_Long (line 16) | class Implementation_Long(AbstractSomeAlgorithm):
    method value (line 21) | def value(self) -> int:

FILE: Chapter_19/some_algorithm/short_version.py
  class Implementation_Short (line 16) | class Implementation_Short(AbstractSomeAlgorithm):
    method value (line 21) | def value(self) -> int:

FILE: Chapter_19/tests/test_all.py
  class TestSomeAlgorithm (line 17) | class TestSomeAlgorithm(unittest.TestCase):
    method test_import_should_see_value (line 19) | def test_import_should_see_value(self):

FILE: Chapter_2/ch02_ex1.py
  class Card (line 12) | class Card:
    method __init__ (line 14) | def __init__(self, rank: str, suit: str) -> None:
    method _points (line 19) | def _points(self) -> Tuple[int, int]:
  class AceCard (line 23) | class AceCard(Card):
    method _points (line 25) | def _points(self) -> Tuple[int, int]:
  class FaceCard (line 29) | class FaceCard(Card):
    method _points (line 31) | def _points(self) -> Tuple[int, int]:

FILE: Chapter_2/ch02_ex2.py
  class Card (line 16) | class Card:
    method __init__ (line 19) | def __init__(self, rank: str, suit: Any) -> None:
    method __eq__ (line 24) | def __eq__(self, other: Any) -> bool:
    method __repr__ (line 32) | def __repr__(self) -> str:
    method __str__ (line 35) | def __str__(self) -> str:
    method _points (line 38) | def _points(self) -> Tuple[int, int]:
  class AceCard (line 42) | class AceCard(Card):
    method _points (line 45) | def _points(self) -> Tuple[int, int]:
  class FaceCard (line 49) | class FaceCard(Card):
    method _points (line 51) | def _points(self) -> Tuple[int, int]:
  class Suit (line 74) | class Suit(str, Enum):

FILE: Chapter_2/ch02_ex3.py
  function card (line 16) | def card(rank: int, suit: Suit) -> Card:
  function card2 (line 51) | def card2(rank: int, suit: Suit) -> Card:
  function card3 (line 70) | def card3(rank: int, suit: Suit) -> Card:
  function card4 (line 108) | def card4(rank: int, suit: Suit) -> Card:
  function card5 (line 126) | def card5(rank: int, suit: Suit) -> Card:
  function card6 (line 145) | def card6(rank: int, suit: Suit) -> Card:
  function card7 (line 176) | def card7(rank: int, suit: Suit) -> Card:
  class CardFactory (line 203) | class CardFactory:
    method rank (line 205) | def rank(self, rank: int) -> "CardFactory":
    method suit (line 216) | def suit(self, suit: Suit) -> Card:

FILE: Chapter_2/ch02_ex4.py
  class Card2 (line 20) | class Card2:
    method __init__ (line 23) | def __init__(self, rank: int, suit: Suit) -> None:
    method __eq__ (line 29) | def __eq__(self, other: Any) -> bool:
    method __repr__ (line 37) | def __repr__(self) -> str:
  class NumberCard2 (line 41) | class NumberCard2(Card2):
    method __init__ (line 43) | def __init__(self, rank: int, suit: Suit) -> None:
  class AceCard2 (line 49) | class AceCard2(Card2):
    method __init__ (line 51) | def __init__(self, rank: int, suit: Suit) -> None:
  class FaceCard2 (line 57) | class FaceCard2(Card2):
    method __init__ (line 59) | def __init__(self, rank: int, suit: Suit) -> None:
  function card9 (line 65) | def card9(rank: int, suit: Suit) -> Card2:
  class Card3 (line 91) | class Card3:
    method __init__ (line 93) | def __init__(self, rank: str, suit: Suit, hard: int, soft: int) -> None:
    method __eq__ (line 99) | def __eq__(self, other: Any) -> bool:
  class NumberCard3 (line 108) | class NumberCard3(Card3):
    method __init__ (line 110) | def __init__(self, rank: int, suit: Suit) -> None:
  class AceCard3 (line 114) | class AceCard3(Card3):
    method __init__ (line 116) | def __init__(self, rank: int, suit: Suit) -> None:
  class FaceCard3 (line 120) | class FaceCard3(Card3):
    method __init__ (line 122) | def __init__(self, rank: int, suit: Suit) -> None:
  function card10 (line 127) | def card10(rank: int, suit: Suit) -> Card3:

FILE: Chapter_2/ch02_ex5.py
  class Deck (line 34) | class Deck:
    method __init__ (line 36) | def __init__(self) -> None:
    method pop (line 40) | def pop(self) -> Card:
  class Deck2 (line 56) | class Deck2(list):
    method __init__ (line 58) | def __init__(self) -> None:
  class Deck3 (line 78) | class Deck3(list):
    method __init__ (line 80) | def __init__(self, decks: int = 1) -> None:
  class Deck3a (line 99) | class Deck3a(list):
    method __init__ (line 101) | def __init__(self, decks: int = 1) -> None:
  class Hand (line 126) | class Hand:
    method __init__ (line 128) | def __init__(self, dealer_card: Card) -> None:
    method hard_total (line 132) | def hard_total(self) -> int:
    method soft_total (line 135) | def soft_total(self) -> int:
    method __repr__ (line 138) | def __repr__(self) -> str:
  class Hand2 (line 157) | class Hand2:
    method __init__ (line 159) | def __init__(self, dealer_card: Card, *cards: Card) -> None:
    method card_append (line 163) | def card_append(self, card: Card) -> None:
    method hard_total (line 166) | def hard_total(self) -> int:
    method soft_total (line 169) | def soft_total(self) -> int:
    method __repr__ (line 172) | def __repr__(self) -> str:
  class Hand3 (line 188) | class Hand3:
    method __init__ (line 191) | def __init__(self, arg1: "Hand3") -> None:
    method __init__ (line 195) | def __init__(self, arg1: Card, arg2: Card, arg3: Card) -> None:
    method __init__ (line 198) | def __init__(
    method __repr__ (line 218) | def __repr__(self) -> str:
  class Hand4 (line 246) | class Hand4:
    method __init__ (line 249) | def __init__(self, arg1: "Hand4") -> None:
    method __init__ (line 253) | def __init__(self, arg1: "Hand4", arg2: Card, *, split: int) -> None:
    method __init__ (line 257) | def __init__(self, arg1: Card, arg2: Card, arg3: Card) -> None:
    method __init__ (line 260) | def __init__(
    method __str__ (line 286) | def __str__(self) -> str:
  class Hand5 (line 304) | class Hand5:
    method __init__ (line 306) | def __init__(self, dealer_card: Card, *cards: Card) -> None:
    method freeze (line 311) | def freeze(other) -> "Hand5":
    method split (line 316) | def split(other, card0, card1) -> Tuple["Hand5", "Hand5"]:
    method __str__ (line 321) | def __str__(self) -> str:
  class BettingStrategy (line 341) | class BettingStrategy:
    method bet (line 343) | def bet(self) -> int:
    method record_win (line 346) | def record_win(self) -> None:
    method record_loss (line 349) | def record_loss(self) -> None:
  class Flat (line 353) | class Flat(BettingStrategy):
    method bet (line 355) | def bet(self) -> int:
  class BettingStrategy2 (line 369) | class BettingStrategy2(metaclass=abc.ABCMeta):
    method bet (line 372) | def bet(self) -> int:
    method record_win (line 375) | def record_win(self):
    method record_loss (line 378) | def record_loss(self):
  class GameStrategy (line 385) | class GameStrategy:
    method insurance (line 387) | def insurance(self, hand: Hand) -> bool:
    method split (line 390) | def split(self, hand: Hand) -> bool:
    method double (line 393) | def double(self, hand: Hand) -> bool:
    method hit (line 396) | def hit(self, hand: Hand) -> bool:
  class Table (line 424) | class Table:
    method __init__ (line 426) | def __init__(self) -> None:
    method place_bet (line 429) | def place_bet(self, amount: int) -> None:
    method get_hand (line 432) | def get_hand(self) -> Hand2:
    method can_insure (line 444) | def can_insure(self, hand: Hand) -> bool:
  class Player (line 451) | class Player:
    method __init__ (line 453) | def __init__(
    method game (line 463) | def game(self):
  class Player2 (line 490) | class Player2(Player):
    method __init__ (line 492) | def __init__(self, **kw) -> None:
    method game (line 499) | def game(self) -> None:
  class Player2x (line 518) | class Player2x(Player):
    method __init__ (line 520) | def __init__(self, **kw) -> None:
    method game (line 527) | def game(self) -> None:
  class Player3 (line 552) | class Player3(Player):
    method __init__ (line 554) | def __init__(
  class ValidPlayer (line 595) | class ValidPlayer:
    method __init__ (line 597) | def __init__(self, table, bet_strategy, game_strategy):
  class Player4 (line 617) | class Player4:
    method __init__ (line 619) | def __init__(

FILE: Chapter_20/combo.py
  function combinations (line 41) | def combinations(n: int, k: int) -> int:

FILE: Chapter_20/src/ch20_ex1.py
  class Suit (line 58) | class Suit(str, Enum):
  class Card (line 68) | class Card:
    method __init__ (line 79) | def __init__(
    method __str__ (line 94) | def __str__(self) -> str:
    method __repr__ (line 97) | def __repr__(self) -> str:
  class FaceCard (line 101) | class FaceCard(Card):
    method __str__ (line 107) | def __str__(self) -> str:
  class AceCard (line 111) | class AceCard(Card):
    method __str__ (line 116) | def __str__(self) -> str:
  function card (line 120) | def card(rank: int, suit: Suit) -> Card:

FILE: Chapter_20/tests/test_ch20.py
  function test_card_factory (line 15) | def test_card_factory():

FILE: Chapter_3/ch03_ex1.py
  class Card (line 25) | class Card:
    method __init__ (line 28) | def __init__(self, rank: str, suit: "Suit", hard: int, soft: int) -> N...
    method __repr__ (line 34) | def __repr__(self) -> str:
    method __str__ (line 37) | def __str__(self) -> str:
  class NumberCard (line 41) | class NumberCard(Card):
    method __init__ (line 43) | def __init__(self, rank: int, suit: "Suit") -> None:
  class AceCard (line 47) | class AceCard(Card):
    method __init__ (line 50) | def __init__(self, rank: int, suit: "Suit") -> None:
  class FaceCard (line 54) | class FaceCard(Card):
    method __init__ (line 56) | def __init__(self, rank: int, suit: "Suit") -> None:
  class Suit (line 61) | class Suit(str, Enum):
  class Card2 (line 99) | class Card2:
    method __init__ (line 102) | def __init__(self, rank: str, suit: "Suit", hard: int, soft: int) -> N...
    method __repr__ (line 108) | def __repr__(self) -> str:
    method __str__ (line 111) | def __str__(self) -> str:
    method __eq__ (line 114) | def __eq__(self, other: Any) -> bool:
    method __hash__ (line 120) | def __hash__(self) -> int:
    method __format__ (line 123) | def __format__(self, format_spec: str) -> str:
    method __bytes__ (line 133) | def __bytes__(self) -> bytes:
  class NumberCard2 (line 142) | class NumberCard2(Card2):
    method __init__ (line 144) | def __init__(self, rank: int, suit: "Suit") -> None:
  class AceCard2 (line 148) | class AceCard2(Card2):
    method __init__ (line 151) | def __init__(self, rank: int, suit: "Suit") -> None:
  class FaceCard2 (line 155) | class FaceCard2(Card2):
    method __init__ (line 157) | def __init__(self, rank: int, suit: "Suit") -> None:
  function card2 (line 162) | def card2(rank: int, suit: Suit) -> Card2:
  class Card3 (line 202) | class Card3:
    method __init__ (line 205) | def __init__(self, rank: str, suit: "Suit", hard: int, soft: int) -> N...
    method __repr__ (line 211) | def __repr__(self) -> str:
    method __str__ (line 214) | def __str__(self) -> str:
    method __eq__ (line 217) | def __eq__(self, other: Any) -> bool:
  class AceCard3 (line 226) | class AceCard3(Card3):
    method __init__ (line 229) | def __init__(self, rank: int, suit: "Suit") -> None:
  class NumberCard3 (line 233) | class NumberCard3(Card3):
    method __init__ (line 235) | def __init__(self, rank: int, suit: "Suit") -> None:
  class FaceCard3 (line 239) | class FaceCard3(Card3):
    method __init__ (line 241) | def __init__(self, rank: int, suit: "Suit") -> None:

FILE: Chapter_3/ch03_ex2.py
  class Suit (line 17) | class Suit(str, Enum):
  class Card (line 24) | class Card(NamedTuple):
    method __str__ (line 28) | def __str__(self) -> str:
    method insure (line 32) | def insure(self) -> bool:
    method hard (line 36) | def hard(self) -> int:
    method soft (line 41) | def soft(self) -> int:
    method _points (line 45) | def _points(self) -> Tuple[int, int]:
  class NumberCard (line 49) | class NumberCard(Card):
    method _points (line 51) | def _points(self) -> Tuple[int, int]:
  class AceCard (line 55) | class AceCard(Card):
    method insure (line 58) | def insure(self) -> bool:
    method _points (line 61) | def _points(self) -> Tuple[int, int]:
  class FaceCard (line 65) | class FaceCard(Card):
    method _points (line 67) | def _points(self) -> Tuple[int, int]:
  function card (line 71) | def card(rank: int, suit: Suit) -> Card:

FILE: Chapter_3/ch03_ex3.py
  class Hand (line 22) | class Hand:
    method __init__ (line 24) | def __init__(self, dealer_card: Card2, *cards: Card2) -> None:
    method __str__ (line 28) | def __str__(self) -> str:
    method __repr__ (line 31) | def __repr__(self) -> str:
    method __format__ (line 35) | def __format__(self, spec: str) -> str:
    method __eq__ (line 40) | def __eq__(self, other: Any) -> bool:
    method __lt__ (line 51) | def __lt__(self, other: Any) -> bool:
    method __le__ (line 59) | def __le__(self, other: Any) -> bool:
    method total (line 69) | def total(self) -> int:
  class FrozenHand (line 77) | class FrozenHand(Hand):
    method __init__ (line 79) | def __init__(self, *args, **kw) -> None:
    method __hash__ (line 89) | def __hash__(self) -> int:
  class Deck (line 93) | class Deck(list):
    method __init__ (line 95) | def __init__(self) -> None:

FILE: Chapter_3/ch03_ex4.py
  class Deck (line 17) | class Deck(list):
    method __init__ (line 19) | def __init__(self) -> None:
  function card_from_bytes (line 160) | def card_from_bytes(buffer: bytes) -> Card2:
  class BlackJackCard_p (line 214) | class BlackJackCard_p:
    method __init__ (line 216) | def __init__(self, rank: int, suit: Suit) -> None:
    method __lt__ (line 220) | def __lt__(self, other: Any) -> bool:
    method __str__ (line 224) | def __str__(self) -> str:
  class BlackJackCard (line 256) | class BlackJackCard:
    method __init__ (line 258) | def __init__(self, rank: int, suit: Suit, hard: int, soft: int) -> None:
    method __lt__ (line 264) | def __lt__(self, other: Any) -> bool:
    method __le__ (line 269) | def __le__(self, other: Any) -> bool:
    method __gt__ (line 275) | def __gt__(self, other: Any) -> bool:
    method __ge__ (line 280) | def __ge__(self, other: Any) -> bool:
    method __eq__ (line 286) | def __eq__(self, other: Any) -> bool:
    method __ne__ (line 292) | def __ne__(self, other: Any) -> bool:
    method __str__ (line 298) | def __str__(self) -> str:
    method __repr__ (line 301) | def __repr__(self) -> str:
  class Ace21Card (line 307) | class Ace21Card(BlackJackCard):
    method __init__ (line 309) | def __init__(self, rank: int, suit: Suit) -> None:
    method __str__ (line 312) | def __str__(self) -> str:
    method __repr__ (line 315) | def __repr__(self) -> str:
  class Face21Card (line 319) | class Face21Card(BlackJackCard):
    method __init__ (line 323) | def __init__(self, rank: int, suit: Suit) -> None:
    method __str__ (line 326) | def __str__(self) -> str:
    method __repr__ (line 329) | def __repr__(self) -> str:
  class Number21Card (line 333) | class Number21Card(BlackJackCard):
    method __init__ (line 335) | def __init__(self, rank: int, suit: Suit) -> None:
  function card21 (line 339) | def card21(rank: int, suit: Suit) -> BlackJackCard:
  class Noisy (line 418) | class Noisy:
    method __del__ (line 420) | def __del__(self) -> None:
  class Parent (line 447) | class Parent:
    method __init__ (line 449) | def __init__(self, *children: 'Child') -> None:
    method __del__ (line 454) | def __del__(self) -> None:
  class Child (line 460) | class Child:
    method __init__ (line 462) | def __init__(self, id: str) -> None:
    method __del__ (line 466) | def __del__(self) -> None:
  class Parent2 (line 497) | class Parent2:
    method __init__ (line 499) | def __init__(self, *children: 'Child2') -> None:
    method __del__ (line 504) | def __del__(self) -> None:
  class Child2 (line 509) | class Child2:
    method __init__ (line 511) | def __init__(self, id: str) -> None:
    method __del__ (line 515) | def __del__(self) -> None:
  class Float_Fail (line 534) | class Float_Fail(float):
    method __init__ (line 536) | def __init__(self, value: float, unit: str) -> None:
  class Float_Units (line 562) | class Float_Units(float):
    method __new__ (line 564) | def __new__(cls, value, unit):
  class Float_Units_Ugly (line 571) | class Float_Units_Ugly(float):
    method __new__ (line 575) | def __new__(cls: Type, value: SupportsFloat, unit: str) -> 'Float_Unit...

FILE: Chapter_3/ch03_ex5.py
  class Float_Fail (line 23) | class Float_Fail(float):
    method __init__ (line 25) | def __init__(self, value: float, unit: str) -> None:
  class Float_Units (line 52) | class Float_Units(float):
    method __new__ (line 54) | def __new__(cls, value, unit):
  class Float_Units_Ugly (line 75) | class Float_Units_Ugly(float):
    method __new__ (line 79) | def __new__(cls: Type, value: SupportsFloat, unit: str) -> "Float_Unit...
  class AddUnitMeta (line 99) | class AddUnitMeta(type):
    method __new__ (line 101) | def __new__(
  class Float_Units2 (line 109) | class Float_Units2(float, metaclass=AddUnitMeta):
    method withUnit (line 111) | def withUnit(self, unit):
  class LoggedMeta (line 132) | class LoggedMeta(type):
    method __new__ (line 134) | def __new__(
  class Logged (line 142) | class Logged(metaclass=LoggedMeta):
  class SomeApplicationClass (line 146) | class SomeApplicationClass(Logged):
    method __init__ (line 148) | def __init__(self, v1: int, v2: int) -> None:

FILE: Chapter_4/ch04_ex1.py
  class Suit (line 24) | class Suit(str, Enum):
  class BlackJackCard (line 31) | class BlackJackCard:
    method __init__ (line 38) | def __init__(self, rank: str, suit: "Suit", hard: int, soft: int) -> N...
    method __repr__ (line 44) | def __repr__(self) -> str:
    method __str__ (line 47) | def __str__(self) -> str:
    method __lt__ (line 57) | def __lt__(self, other: Any) -> bool:
    method __le__ (line 63) | def __le__(self, other: Any) -> bool:
    method __eq__ (line 70) | def __eq__(self, other: Any) -> bool:
  class Ace21Card (line 79) | class Ace21Card(BlackJackCard):
    method __init__ (line 82) | def __init__(self, rank: int, suit: Suit) -> None:
    method __repr__ (line 85) | def __repr__(self) -> str:
  class Face21Card (line 89) | class Face21Card(BlackJackCard):
    method __init__ (line 92) | def __init__(self, rank: int, suit: Suit) -> None:
    method __repr__ (line 96) | def __repr__(self) -> str:
  class Number21Card (line 101) | class Number21Card(BlackJackCard):
    method __init__ (line 104) | def __init__(self, rank: int, suit: Suit) -> None:
    method __repr__ (line 107) | def __repr__(self) -> str:
  function card21 (line 111) | def card21(rank: int, suit: Suit) -> BlackJackCard:
  function compare (line 122) | def compare(a: Any, b: Any) -> None:
  class Deck (line 172) | class Deck(list):
    method __init__ (line 174) | def __init__(
  class AceCard2 (line 186) | class AceCard2(NamedTuple):
    method __str__ (line 192) | def __str__(self) -> str:
  class FaceCard2 (line 196) | class FaceCard2(NamedTuple):
    method __str__ (line 202) | def __str__(self) -> str:
  class NumberCard2 (line 206) | class NumberCard2(NamedTuple):
    method __str__ (line 212) | def __str__(self) -> str:
  function card2 (line 216) | def card2(rank: int, suit: Suit) -> Union[AceCard2, FaceCard2, NumberCar...
  class AceCard3 (line 287) | class AceCard3:
  class FaceCard3 (line 295) | class FaceCard3:
  class NumberCard3 (line 303) | class NumberCard3:
  function card3 (line 310) | def card3(rank, suit) -> Union[AceCard3, FaceCard3, NumberCard3]:

FILE: Chapter_4/ch04_ex2.py
  class Hand (line 21) | class Hand:
    method __init__ (line 23) | def __init__(
    method __str__ (line 31) | def __str__(self) -> str:
    method __repr__ (line 34) | def __repr__(self) -> str:
    method card (line 42) | def card(self) -> List[BlackJackCard]:
    method card (line 46) | def card(self, aCard: BlackJackCard) -> None:
    method card (line 50) | def card(self) -> None:
    method split (line 53) | def split(self, deck: Deck) -> "Hand":
  class Hand_Lazy (line 63) | class Hand_Lazy(Hand):
    method total (line 66) | def total(self) -> int:
    method card (line 74) | def card(self) -> List[BlackJackCard]:
    method card (line 78) | def card(self, aCard: BlackJackCard) -> None:
    method card (line 82) | def card(self) -> None:
  class Hand_Eager (line 107) | class Hand_Eager(Hand):
    method __init__ (line 109) | def __init__(
    method card (line 125) | def card(self) -> List[BlackJackCard]:
    method card (line 129) | def card(self, aCard: BlackJackCard) -> None:
    method card (line 136) | def card(self) -> None:
    method _set_total (line 143) | def _set_total(self) -> None:

FILE: Chapter_4/ch04_ex3.py
  class RateTimeDistance (line 22) | class RateTimeDistance:
    method __post_init__ (line 28) | def __post_init__(self) -> None:
  class RTD_Dynamic (line 57) | class RTD_Dynamic:
    method __init__ (line 59) | def __init__(self) -> None:
    method __repr__ (line 68) | def __repr__(self) -> str:
    method __setattr__ (line 78) | def __setattr__(self, name: str, value: float) -> None:
  class PersistentState (line 122) | class PersistentState:
  class StateManager (line 127) | class StateManager:
    method __init__ (line 130) | def __init__(self, base: Path) -> None:
    method __get__ (line 133) | def __get__(self, instance: PersistentState, owner: Type) -> Path:
  class PersistentClass (line 141) | class PersistentClass(PersistentState):
    method __init__ (line 144) | def __init__(self, a: int, b: float) -> None:
    method calculate (line 150) | def calculate(self, c: float) -> float:
    method __str__ (line 155) | def __str__(self) -> str:
  class Conversion (line 172) | class Conversion:
    method __get__ (line 177) | def __get__(self, instance: Any, owner: type) -> float:
    method __set__ (line 180) | def __set__(self, instance: Any, value: float) -> None:
  class Standard (line 184) | class Standard(Conversion):
  class Speed (line 189) | class Speed(Conversion):
  class KPH (line 193) | class KPH(Standard, Speed):
  class Knots (line 197) | class Knots(Speed):
  class MPH (line 201) | class MPH(Speed):
  class Trip (line 205) | class Trip:
    method __init__ (line 210) | def __init__(
    method __str__ (line 228) | def __str__(self) -> str:

FILE: Chapter_4/ch04_ex4.py
  class Card (line 14) | class Card:
    method __init__ (line 17) | def __init__(cls, rank: int, suit: Suit) -> None:
    method __setattr__ (line 20) | def __setattr__(self, name: str, value: Any) -> None:
    method __getattr__ (line 23) | def __getattr__(self, name: str) -> Any:
  class RTD_Solver (line 87) | class RTD_Solver:
    method __init__ (line 89) | def __init__(
    method __getattr__ (line 99) | def __getattr__(self, name: str) -> float:
  class SuperSecret (line 120) | class SuperSecret:
    method __init__ (line 122) | def __init__(self, hidden: Any, exposed: Any) -> None:
    method __getattribute__ (line 126) | def __getattribute__(self, item: str):

FILE: Chapter_4/ch04_ex5.py
  class RTD (line 16) | class RTD:
    method compute (line 21) | def compute(self) -> "RTD":
  class Suit (line 44) | class Suit(str, Enum):
  class Card (line 52) | class Card:
    method points (line 57) | def points(self) -> int:
  class Ace (line 61) | class Ace(Card):
    method points (line 64) | def points(self) -> int:
  class Face (line 68) | class Face(Card):
    method points (line 71) | def points(self) -> int:
  function deck (line 75) | def deck() -> Iterator[Card]:

FILE: Chapter_5/ch05_ex1.py
  class Card (line 18) | class Card:
  class Hand (line 22) | class Hand(list):
    method __init__ (line 23) | def __init__(self, *cards: Card) -> None:
  class AbstractBettingStrategy (line 27) | class AbstractBettingStrategy(metaclass=ABCMeta):
    method bet (line 30) | def bet(self, hand: Hand) -> int:
    method record_win (line 34) | def record_win(self, hand: Hand) -> None:
    method record_loss (line 38) | def record_loss(self, hand: Hand) -> None:
  class AbstractBettingStrategy2 (line 41) | class AbstractBettingStrategy2(ABC):
    method bet (line 44) | def bet(self, hand: Hand) -> int:
    method record_win (line 48) | def record_win(self, hand: Hand) -> None:
    method record_loss (line 52) | def record_loss(self, hand: Hand) -> None:
    method __subclasshook__ (line 56) | def __subclasshook__(cls, subclass: type) -> bool:
  class Simple (line 96) | class Simple(AbstractBettingStrategy):
    method bet (line 98) | def bet(self, hand: Hand) -> int:
    method record_win (line 101) | def record_win(self, hand: Hand) -> None:
    method record_loss (line 104) | def record_loss(self, hand: Hand) -> None:
  class LikeAbstract (line 113) | class LikeAbstract:
    method aMethod (line 114) | def aMethod(self, arg: int) -> int:
  class LikeConcrete (line 119) | class LikeConcrete(LikeAbstract):
    method aMethod (line 120) | def aMethod(self, arg1: str, arg2: Tuple[int, int]) -> Iterator[Any]:

FILE: Chapter_6/ch06_ex1.py
  class Power1 (line 18) | class Power1:
    method __call__ (line 20) | def __call__(self, x: int, n: int) -> int:
  class Power2 (line 37) | class Power2(CallableClass):  # type: ignore
    method __call_ (line 39) | def __call_(self, x: int, n: int) -> int:
  class Power3 (line 58) | class Power3:
    method __call_ (line 60) | def __call_(self, x: int, n: int) -> int:
  class Power4 (line 81) | class Power4:
    method __call__ (line 83) | def __call__(self, x: int, n: int) -> int:
  class Power4i (line 103) | class Power4i:
    method __call__ (line 105) | def __call__(self, x: int, n: int) -> int:
  class Power5 (line 128) | class Power5:
    method __init__ (line 130) | def __init__(self) -> None:
    method __call__ (line 133) | def __call__(self, x: int, n: int) -> int:
  function pow6 (line 157) | def pow6(x: int, n: int) -> int:
  function performance (line 172) | def performance() -> None:
  class BettingStrategy (line 239) | class BettingStrategy:
    method __init__ (line 241) | def __init__(self) -> None:
    method win (line 246) | def win(self) -> int:
    method win (line 250) | def win(self, value: int) -> None:
    method loss (line 255) | def loss(self) -> int:
    method loss (line 259) | def loss(self, value: int) -> None:
    method __call__ (line 262) | def __call__(self) -> int:
  class BettingMartingale (line 278) | class BettingMartingale(BettingStrategy):
    method __init__ (line 280) | def __init__(self) -> None:
    method win (line 286) | def win(self) -> int:
    method win (line 290) | def win(self, value: int) -> None:
    method loss (line 295) | def loss(self) -> int:
    method loss (line 299) | def loss(self, value: int) -> None:
    method __call__ (line 303) | def __call__(self) -> int:
  class BettingMartingale2 (line 326) | class BettingMartingale2(BettingStrategy):
    method __init__ (line 328) | def __init__(self) -> None:
    method __setattr__ (line 333) | def __setattr__(self, name: str, value: int) -> None:
    method __call__ (line 340) | def __call__(self) -> int:

FILE: Chapter_6/ch06_ex2.py
  function slow (line 17) | def slow(source="itmaybeahack.com.bkup-Feb-2012.gz") -> int:
  class Debugging (line 88) | class Debugging:
    method __init__ (line 90) | def __init__(self, aName=None):
    method __enter__ (line 93) | def __enter__(self):
    method __exit__ (line 97) | def __exit__(self, exc_type, exc_value, traceback):
  class KnownSequence (line 133) | class KnownSequence:
    method __init__ (line 135) | def __init__(self, seed: int = 0) -> None:
    method __enter__ (line 138) | def __enter__(self) -> 'KnownSequence':
    method __exit__ (line 143) | def __exit__(
  class Suit (line 172) | class Suit(Enum):
  class Card (line 178) | class Card(NamedTuple):
  class Deck (line 182) | class Deck(list):
    method __init__ (line 184) | def __init__(self, size: int = 1) -> None:
  class Deterministic_Deck (line 194) | class Deterministic_Deck:
    method __init__ (line 196) | def __init__(self, *args, **kw) -> None:
    method __enter__ (line 200) | def __enter__(self) -> Deck:
    method __exit__ (line 205) | def __exit__(
  class Deck2 (line 236) | class Deck2(list, KnownSequence):
    method __init__ (line 238) | def __init__(self, size: int = 1) -> None:
    method pop (line 246) | def pop(self, *args, **kw) -> Card:
  class Updating (line 275) | class Updating:
    method __init__ (line 277) | def __init__(self, target: Path) -> None:
    method __enter__ (line 281) | def __enter__(self) -> None:
    method __exit__ (line 292) | def __exit__(
  function some_update (line 312) | def some_update(important_path):

FILE: Chapter_7/ch07_ex1.py
  class Suit (line 29) | class Suit(str, Enum):
  class BlackjackCard_T (line 46) | class BlackjackCard_T(NamedTuple):
    method is_ace (line 51) | def is_ace(self) -> bool:
  function card (line 55) | def card(rank: int, suit: Suit) -> BlackjackCard:
  function card_t (line 70) | def card_t(rank: int, suit: Suit) -> BlackjackCard_T:
  class AceCard (line 99) | class AceCard(BlackjackCard):
    method is_ace (line 100) | def is_ace(self) -> bool:
  class AceCard_T (line 103) | class AceCard_T(BlackjackCard_T):
    method is_ace (line 104) | def is_ace(self) -> bool:
  class Card (line 145) | class Card(NamedTuple):
  class MultiDeck (line 154) | class MultiDeck(list):
    method __init__ (line 158) | def __init__(self, size: int = 5) -> None:
  function get_options (line 190) | def get_options(argv: List[str] = sys.argv[1:]) -> ChainMap:
  function dice_examples (line 335) | def dice_examples(n: int=12, seed: Any=None) -> DefaultDict[int, List]:
  function value_iterator (line 361) | def value_iterator(count=100, seed=4000) -> Iterable[str]:
  function freq_ordered (line 372) | def freq_ordered(values: Iterable[T]) -> Dict[int, List[T]]:
  function bag_demo (line 423) | def bag_demo() -> None:

FILE: Chapter_7/ch07_ex2.py
  function mean (line 18) | def mean(outcomes: List[float]) -> float:
  function stdev (line 22) | def stdev(outcomes: List[float]) -> float:
  class StatsList (line 40) | class StatsList(list):
    method __init__ (line 42) | def __init__(self, iterable: Optional[Iterable[float]]) -> None:
    method mean (line 46) | def mean(self) -> float:
    method stdev (line 50) | def stdev(self) -> float:
  function data_gen (line 71) | def data_gen() -> int:
  function demo_statslist (line 75) | def demo_statslist() -> None:
  class Explore (line 89) | class Explore(list):
    method __getitem__ (line 92) | def __getitem__(self, index):
  class StatsList2 (line 117) | class StatsList2(list):
    method __init__ (line 120) | def __init__(self, iterable: Optional[Iterable[float]]) -> None:
    method _new (line 128) | def _new(self, value: float) -> None:
    method _rmv (line 133) | def _rmv(self, value: float) -> None:
    method insert (line 138) | def insert(self, index: int, value: float) -> None:
    method pop (line 142) | def pop(self, index: int = 0) -> None:
    method append (line 147) | def append(self, value: float) -> None:
    method extend (line 151) | def extend(self, sequence: Iterable[float]) -> None:
    method remove (line 156) | def remove(self, value: float) -> None:
    method __iadd__ (line 160) | def __iadd__(self, sequence: Iterable[float]) -> "StatsList2":
    method __add__ (line 165) | def __add__(self, sequence: Iterable[float]) -> "StatsList2":
    method mean (line 176) | def mean(self) -> float:
    method stdev (line 180) | def stdev(self) -> float:
    method __setitem__ (line 184) | def __setitem__(self, index: int, value: float) -> None:
    method __setitem__ (line 188) | def __setitem__(self, index: slice, value: Iterable[float]) -> None:
    method __setitem__ (line 191) | def __setitem__(self, index, value) -> None:
    method __delitem__ (line 206) | def __delitem__(self, index: Union[int, slice]) -> None:
  class StatsList3 (line 295) | class StatsList3:
    method __init__ (line 297) | def __init__(self) -> None:
    method append (line 303) | def append(self, value: float) -> None:
    method __getitem__ (line 311) | def __getitem__(self, index: int) -> float:
    method mean (line 315) | def mean(self) -> float:
    method stdev (line 319) | def stdev(self) -> float:
  class StatsCounter (line 340) | class StatsCounter(Counter):
    method mean (line 343) | def mean(self) -> float:
    method stdev (line 349) | def stdev(self) -> float:
    method median (line 356) | def median(self) -> Any:
    method median2 (line 361) | def median2(self) -> Optional[float]:

FILE: Chapter_7/ch07_ex3.py
  class Comparable (line 31) | class Comparable(metaclass=ABCMeta):
    method __lt__ (line 34) | def __lt__(self, other: Any) -> bool:
    method __ge__ (line 37) | def __ge__(self, other: Any) -> bool:
  class TreeNode (line 45) | class TreeNode:
    method __init__ (line 51) | def __init__(
    method parent (line 66) | def parent(self) -> Optional["TreeNode"]:
    method parent (line 70) | def parent(self, value: "TreeNode") -> None:
    method __repr__ (line 73) | def __repr__(self) -> str:
    method find (line 76) | def find(self, item: Comparable) -> "TreeNode":
    method __iter__ (line 88) | def __iter__(self) -> Iterator[Comparable]:
    method add (line 96) | def add(self, item: Comparable) -> None:
    method remove (line 113) | def remove(self, item: Comparable) -> None:
    method _least (line 138) | def _least(self) -> "TreeNode":
    method _replace (line 143) | def _replace(self, new: Optional["TreeNode"] = None) -> None:
  class Tree (line 153) | class Tree(collections.abc.MutableSet):
    method __init__ (line 155) | def __init__(self, source: Iterable[Comparable] = None) -> None:
    method add (line 163) | def add(self, item: Comparable) -> None:
    method discard (line 167) | def discard(self, item: Comparable) -> None:
    method __contains__ (line 177) | def __contains__(self, item: Any) -> bool:
    method __iter__ (line 184) | def __iter__(self) -> Iterator[Comparable]:
    method __len__ (line 190) | def __len__(self) -> int:

FILE: Chapter_7/ch07_ex4.py
  function performance (line 17) | def performance() -> None:

FILE: Chapter_8/ch08_ex1.py
  function trace (line 16) | def trace(frame, event, arg):
  class NoisyFloat (line 24) | class NoisyFloat(float):
    method __add__ (line 26) | def __add__(self, other: float) -> 'NoisyFloat':
    method __radd__ (line 30) | def __radd__(self, other: float) -> 'NoisyFloat':
  class FixedPoint (line 51) | class FixedPoint(numbers.Rational):
    method __init__ (line 54) | def __init__(self, value: Union['FixedPoint', int, float], scale: int ...
    method __str__ (line 71) | def __str__(self) -> str:
    method __repr__ (line 74) | def __repr__(self) -> str:
    method __format__ (line 77) | def __format__(self, specification: str) -> str:
    method numerator (line 82) | def numerator(self) -> int:
    method denominator (line 85) | def denominator(self) -> int:
    method __add__ (line 88) | def __add__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __sub__ (line 99) | def __sub__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __mul__ (line 110) | def __mul__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __truediv__ (line 119) | def __truediv__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __floordiv__ (line 126) | def __floordiv__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __mod__ (line 133) | def __mod__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __pow__ (line 140) | def __pow__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __abs__ (line 147) | def __abs__(self) -> 'FixedPoint':
    method __float__ (line 150) | def __float__(self) -> float:
    method __int__ (line 153) | def __int__(self) -> int:
    method __trunc__ (line 156) | def __trunc__(self) -> int:
    method __ceil__ (line 159) | def __ceil__(self) -> int:
    method __floor__ (line 162) | def __floor__(self) -> int:
    method __round__ (line 167) | def __round__(self, ndigits: Optional[int] = 0) -> Any:
    method __neg__ (line 170) | def __neg__(self) -> 'FixedPoint':
    method __pos__ (line 173) | def __pos__(self) -> 'FixedPoint':
    method __eq__ (line 178) | def __eq__(self, other: Any) -> bool:
    method __ne__ (line 187) | def __ne__(self, other: Any) -> bool:
    method __le__ (line 190) | def __le__(self, other: 'FixedPoint') -> bool:
    method __lt__ (line 193) | def __lt__(self, other: 'FixedPoint') -> bool:
    method __ge__ (line 196) | def __ge__(self, other: 'FixedPoint') -> bool:
    method __gt__ (line 199) | def __gt__(self, other: 'FixedPoint') -> bool:
    method __hash__ (line 202) | def __hash__(self) -> int:
    method __radd__ (line 221) | def __radd__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rsub__ (line 232) | def __rsub__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rmul__ (line 243) | def __rmul__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rtruediv__ (line 252) | def __rtruediv__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rfloordiv__ (line 259) | def __rfloordiv__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rmod__ (line 266) | def __rmod__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method __rpow__ (line 273) | def __rpow__(self, other: Union['FixedPoint', int]) -> 'FixedPoint':
    method round_to (line 280) | def round_to(self, new_scale: int) -> 'FixedPoint':

FILE: Chapter_9/ch09_ex1.py
  class Angle (line 22) | class Angle(float):
    method from_radians (line 26) | def from_radians(value: float) -> "Angle":
    method __init__ (line 29) | def __init__(self, degrees: float) -> None:
    method radians (line 33) | def radians(self) -> float:
    method degrees (line 37) | def degrees(self) -> float:
  class Suit (line 61) | class Suit(Enum):
  class CardTO (line 74) | class CardTO:
    method __init__ (line 77) | def __init__(self, rank: int, suit: Suit) -> None:
    method __eq__ (line 81) | def __eq__(self, other: Any) -> bool:
    method __lt__ (line 84) | def __lt__(self, other: Any) -> bool:
    method __str__ (line 87) | def __str__(self) -> str:
  class CardDC (line 112) | class CardDC:
    method __eq__ (line 116) | def __eq__(self, other: Any) -> bool:
    method __lt__ (line 119) | def __lt__(self, other: Any) -> bool:
    method __le__ (line 122) | def __le__(self, other: Any) -> bool:
    method __str__ (line 125) | def __str__(self) -> str:
  class Deck (line 149) | class Deck(list):
    method __init__ (line 151) | def __init__(self, size: int = 1) -> None:
  class EnumDomain (line 168) | class EnumDomain:
    method domain (line 171) | def domain(cls: Type) -> List[str]:
  class SuitD (line 175) | class SuitD(str, EnumDomain, Enum):
  function debug (line 214) | def debug(function: F) -> F:
  function ackermann (line 227) | def ackermann(m: int, n: int) -> int:
  function debug2 (line 249) | def debug2(function: F) -> F:
  function ackermann2 (line 263) | def ackermann2(m: int, n: int) -> int:
  function simpler (line 275) | def simpler(x: int, y: int) -> int:
  function decorator (line 292) | def decorator(config) -> Callable[[F], F]:
  function debug_named (line 304) | def debug_named(log_name: str) -> Callable[[F], F]:
  function ackermann3 (line 322) | def ackermann3(m: int, n: int) -> int:
  function standard (line 346) | def standard(class_: Type) -> Type:
  function nonstandard (line 351) | def nonstandard(based_on: Type) -> Callable[[Type], Type]:
  class Unit (line 360) | class Unit:
    method value (line 364) | def value(class_, value: float) -> float:
    method convert (line 370) | def convert(class_, value: float) -> float:
  class INCH (line 377) | class INCH(Unit):
  class FOOT (line 383) | class FOOT(Unit):
  function audit (line 399) | def audit(method: F) -> F:
  class Hand (line 419) | class Hand:
    method __init__ (line 421) | def __init__(self, *cards: CardDC) -> None:
    method __iadd__ (line 425) | def __iadd__(self, card: CardDC) -> "Hand":
    method __repr__ (line 430) | def __repr__(self) -> str:
  function memento (line 471) | def memento(class_: Type) -> Type:
  class StatefulClass (line 484) | class StatefulClass:
    method __init__ (line 486) | def __init__(self, value: Any) -> None:
    method __repr__ (line 489) | def __repr__(self) -> str:
  class Memento (line 500) | class Memento:
    method memento (line 502) | def memento(self) -> str:
  class StatefulClass2 (line 506) | class StatefulClass2(Memento):
    method __init__ (line 508) | def __init__(self, value):
    method __repr__ (line 511) | def __repr__(self):

FILE: Chapter_9/ch09_ex2.py
  class UglyClass1 (line 18) | class UglyClass1:
    method __init__ (line 20) | def __init__(self) -> None:
    method method (line 24) | def method(self, *args: Any) -> int:
  class UglyClass2 (line 30) | class UglyClass2:
    method __init__ (line 33) | def __init__(self) -> None:
    method method (line 36) | def method(self, *args: Any) -> int:
  function logged (line 45) | def logged(class_: Type) -> Type:
  class SomeClass (line 51) | class SomeClass:
    method __init__ (line 53) | def __init__(self) -> None:
    method method (line 56) | def method(self, *args: Any) -> int:
  class LoggedInstance (line 62) | class LoggedInstance:
    method __new__ (line 65) | def __new__(cls):
  class SomeClass2 (line 71) | class SomeClass2(LoggedInstance):
    method __init__ (line 73) | def __init__(self) -> None:
    method method (line 76) | def method(self, *args: Any) -> int:
  class LoggedClassMeta (line 84) | class LoggedClassMeta(type):
    method __new__ (line 86) | def __new__(cls, name, bases, namespace, **kwds):
  class LoggedClass (line 92) | class LoggedClass(metaclass=LoggedClassMeta):
  class SomeClass3 (line 97) | class SomeClass3(LoggedClass):
    method __init__ (line 99) | def __init__(self) -> None:
    method method (line 102) | def method(self, *args: Any) -> int:
  class LoggedWithHook (line 107) | class LoggedWithHook:
    method __init_subclass__ (line 108) | def __init_subclass__(cls, name=None):
  class SomeClass4 (line 112) | class SomeClass4(LoggedWithHook):
    method __init__ (line 114) | def __init__(self) -> None:
    method method (line 117) | def method(self, *args: Any) -> int:
  class SomeClass4s (line 121) | class SomeClass4s(LoggedWithHook, name='special'):
    method __init__ (line 123) | def __init__(self) -> None:
    method method (line 126) | def method(self, *args: Any) -> int:

FILE: stubs/sqlite3.pyi
  function connect (line 7) | def connect(

FILE: test_all.py
  function package_module_iter (line 27) | def package_module_iter(packages: Iterable[Path]) -> Iterator[Tuple[Path...
  function run (line 58) | def run(pkg_mod_iter: Iterable[Tuple[Path, Iterable[Path]]]) -> None:
  function run_doctest_suite (line 74) | def run_doctest_suite(pkg_mod_iter: Iterable[Tuple[Path, Iterable[Path]]...
  class PytestExit (line 92) | class PytestExit(int, Enum):
  function run_pytest_suite (line 100) | def run_pytest_suite(pkg_mod_iter: Iterable[Tuple[Path, Iterable[Path]]]...
  function run_performance (line 115) | def run_performance(pkg_mod_iter: Iterable[Tuple[Path, Iterable[Path]]])...
  function master_test_suite (line 131) | def master_test_suite(pkg_mod_iter: Iterable[Tuple[Path, Iterable[Path]]...
  function chap_key (line 146) | def chap_key(name: Path) -> int:
Condensed preview — 142 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (636K chars).
[
  {
    "path": ".pylintrc",
    "chars": 17222,
    "preview": "[MASTER]\n\n# A comma-separated list of package or module names from where C extensions may\n# be loaded. Extensions are lo"
  },
  {
    "path": "Chapter_1/ch01_ex1.py",
    "chars": 523,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_1/ch01_ex2.py",
    "chars": 640,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_1/ch01_ex3.py",
    "chars": 543,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_1/ch01_ex4.py",
    "chars": 574,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_1/ch01_ex5.py",
    "chars": 1401,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_1/getting_started.rst",
    "chars": 2176,
    "preview": "In order to run the examples mentioned in this book you require the following software:\n\n- Python version 3.7 or higher "
  },
  {
    "path": "Chapter_10/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_10/ch10_bonus.py",
    "chars": 3410,
    "preview": "\"\"\"\nEnumerate all Blackjack outcomes with player mirroring the dealer choice.\nNote that player going bust first is a los"
  },
  {
    "path": "Chapter_10/ch10_ex1.py",
    "chars": 23767,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex2.py",
    "chars": 1201,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex2a.py",
    "chars": 3348,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex2b.py",
    "chars": 2561,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex2c.py",
    "chars": 2260,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex3.py",
    "chars": 5243,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex4.py",
    "chars": 13472,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex5.py",
    "chars": 19224,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_10/ch10_ex6.py",
    "chars": 7610,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_11/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_11/ch11_ex1.py",
    "chars": 4236,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_11/ch11_ex2.py",
    "chars": 18749,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_12/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_12/ch12_ex1.py",
    "chars": 7914,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_12/ch12_ex2.py",
    "chars": 1877,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_12/ch12_ex3.py",
    "chars": 7073,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_12/ch12_ex4.py",
    "chars": 4940,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_13/cards_openapi.json",
    "chars": 2768,
    "preview": "{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"description\": \"Deals simple hands of cards\",\n    \"version\": \"2019.02\",\n    \"tit"
  },
  {
    "path": "Chapter_13/ch13_e1_ex2.py",
    "chars": 3586,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_e1_ex3.py",
    "chars": 10079,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_e1_ex4.py",
    "chars": 15189,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex1.py",
    "chars": 6889,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex2.py",
    "chars": 5560,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex3.py",
    "chars": 6813,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex4.py",
    "chars": 2860,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex5.py",
    "chars": 6831,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/ch13_ex6.py",
    "chars": 3245,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_13/dice_openapi.json",
    "chars": 2699,
    "preview": "{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"description\": \"Rolls dice\",\n    \"version\": \"2019.02\",\n    \"title\": \"Chapter 13."
  },
  {
    "path": "Chapter_13/dominoes_openapi.json",
    "chars": 2738,
    "preview": "{\n  \"openapi\": \"3.0.0\",\n  \"info\": {\n    \"description\": \"Deals simple hands of dominoes\",\n    \"version\": \"2019.02\",\n    \""
  },
  {
    "path": "Chapter_13/simulation_model.py",
    "chars": 3541,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_14/ch14_ex1.py",
    "chars": 5020,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/ch14_ex2.py",
    "chars": 5464,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/ch14_ex3.py",
    "chars": 6590,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/ch14_ex4.py",
    "chars": 6921,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/ch14_ex5.py",
    "chars": 8081,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/ch14_ex6.py",
    "chars": 5106,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/simulation_model.py",
    "chars": 3880,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_14/someapp.config",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_15/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_15/ch15_ex1.py",
    "chars": 9555,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_15/ch15_ex2.py",
    "chars": 5305,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_16/ch16_ex1.py",
    "chars": 2610,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex10.py",
    "chars": 1210,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex2.py",
    "chars": 1628,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex3.py",
    "chars": 1532,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex4.py",
    "chars": 2518,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex5.py",
    "chars": 3843,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex6.py",
    "chars": 2839,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex7.py",
    "chars": 1068,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex8.py",
    "chars": 2025,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_16/ch16_ex9.py",
    "chars": 2944,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_17/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_17/ch17_data.csv",
    "chars": 101,
    "preview": "rate_in,time_in,distance_in,rate_out,time_out,distance_out\n2,3,,2,3,6\n5,,7,5,1.4,7\n,11,13,1.18,11,13\n"
  },
  {
    "path": "Chapter_17/ch17_ex1.py",
    "chars": 14511,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_17/ch17_ex2.py",
    "chars": 4072,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_17/test_ch17.py",
    "chars": 12559,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_18/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_18/ch18_demo.py",
    "chars": 181,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_18/ch18_ex1.py",
    "chars": 7601,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_18/ch18_ex2.py",
    "chars": 2986,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_18/ch18_ex3.py",
    "chars": 12096,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_18/ch18app.yaml",
    "chars": 96,
    "preview": "# User configuration file: Chapter_18/ch18app.yaml\ndealer_rule: Hit17\nsplit_rule: NoReSplitAces\n"
  },
  {
    "path": "Chapter_18/opt/ch18app.yaml",
    "chars": 104,
    "preview": "# Installation-wide configuration file: Chapter_18/opt/ch18app.yaml\n\nrounds: 100\nsamples: 100\nstake: 50\n"
  },
  {
    "path": "Chapter_19/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_19/ch19_ex1.py",
    "chars": 369,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/ch19_ex2.py",
    "chars": 295,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/some_algorithm/__init__.py",
    "chars": 858,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/some_algorithm/abstraction.py",
    "chars": 197,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/some_algorithm/long_version.py",
    "chars": 417,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/some_algorithm/short_version.py",
    "chars": 415,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_19/tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_19/tests/test_all.py",
    "chars": 509,
    "preview": "# A Test Module\n# ----------------\n\n\"\"\"Test all features of some_algorithm.\n\nRequires some_algorithm package be on the P"
  },
  {
    "path": "Chapter_2/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_2/ch02_ex1.py",
    "chars": 1205,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_2/ch02_ex2.py",
    "chars": 2819,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_2/ch02_ex3.py",
    "chars": 7979,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_2/ch02_ex4.py",
    "chars": 4325,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_2/ch02_ex5.py",
    "chars": 17696,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_20/README.rst",
    "chars": 423,
    "preview": "A Mini Python Project\n=====================\n\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Ori"
  },
  {
    "path": "Chapter_20/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_20/combo.py",
    "chars": 1892,
    "preview": "# #############\n# Combinations\n# #############\n#\n# ..  contents::\n#\n# Definition\n# ==========\n#\n# For some deeper statis"
  },
  {
    "path": "Chapter_20/combo.py.html",
    "chars": 15822,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.or"
  },
  {
    "path": "Chapter_20/combo.py.txt",
    "chars": 1824,
    "preview": "#############\nCombinations\n#############\n\n..  contents::\n\nDefinition\n==========\n\nFor some deeper statistical calculation"
  },
  {
    "path": "Chapter_20/docs/Makefile",
    "chars": 580,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHI"
  },
  {
    "path": "Chapter_20/docs/conf.py",
    "chars": 5558,
    "preview": "# -*- coding: utf-8 -*-\n#\n# Configuration file for the Sphinx documentation builder.\n#\n# This file does only contain a s"
  },
  {
    "path": "Chapter_20/docs/implementation.rst",
    "chars": 397,
    "preview": "Implementation\n==============\n\nHere's a reference to the `inception document <_static/inception_doc/index.html>`_\n\nHere'"
  },
  {
    "path": "Chapter_20/docs/index.rst",
    "chars": 478,
    "preview": ".. Chapter 20 documentation master file, created by\n   sphinx-quickstart on Wed Apr  3 16:18:57 2019.\n   You can adapt t"
  },
  {
    "path": "Chapter_20/docs/user_story.rst",
    "chars": 179,
    "preview": "..  _user_story:\n\nUser Stories\n============\n\nThe user generally has three tasks: customize the simulation's parameters,\n"
  },
  {
    "path": "Chapter_20/src/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_20/src/ch20_ex1.py",
    "chars": 4259,
    "preview": "#!/usr/bin/env python3.7\n# Mastering Object-Oriented Python 2e\n#\n# Code Examples for Mastering Object-Oriented Python 2n"
  },
  {
    "path": "Chapter_20/tests/test_ch20.py",
    "chars": 412,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_3/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_3/ch03_ex1.py",
    "chars": 6799,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_3/ch03_ex2.py",
    "chars": 2487,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_3/ch03_ex3.py",
    "chars": 3718,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_3/ch03_ex4.py",
    "chars": 17657,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_3/ch03_ex5.py",
    "chars": 4228,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_4/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_4/ch04_ex1.py",
    "chars": 12613,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_4/ch04_ex2.py",
    "chars": 4383,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_4/ch04_ex3.py",
    "chars": 6777,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_4/ch04_ex4.py",
    "chars": 5091,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_4/ch04_ex5.py",
    "chars": 2854,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_5/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_5/ch05_ex1.py",
    "chars": 3731,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_5/ch05_ex2.py",
    "chars": 1379,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_6/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_6/ch06_ex1.py",
    "chars": 9680,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_6/ch06_ex2.py",
    "chars": 11156,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_7/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_7/ch07_defaults.json",
    "chars": 72,
    "preview": "{\n    \"decks\": 6,\n    \"table_limit\": 50,\n    \"playerclass\": \"Passive\"\n}\n"
  },
  {
    "path": "Chapter_7/ch07_ex1.py",
    "chars": 12321,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_7/ch07_ex2.py",
    "chars": 10717,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_7/ch07_ex3.py",
    "chars": 7980,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_7/ch07_ex4.py",
    "chars": 1031,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_8/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_8/ch08_ex1.py",
    "chars": 11636,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_9/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "Chapter_9/ch09_ex1.py",
    "chars": 11952,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "Chapter_9/ch09_ex2.py",
    "chars": 3756,
    "preview": "#!/usr/bin/env python3.7\n\"\"\"\nMastering Object-Oriented Python 2e\n\nCode Examples for Mastering Object-Oriented Python 2nd"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (c) 2019 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
  },
  {
    "path": "README.md",
    "chars": 5083,
    "preview": "\n\n\n# Mastering Object-Oriented Python - Second Edition \n\n<a href=\"https://www.packtpub.com/programming/mastering-object-"
  },
  {
    "path": "data/ch17_data.csv",
    "chars": 101,
    "preview": "rate_in,time_in,distance_in,rate_out,time_out,distance_out\n2,3,,2,3,6\n5,,7,5,1.4,7\n,11,13,1.18,11,13\n"
  },
  {
    "path": "data/ch17_sample.csv",
    "chars": 39,
    "preview": "not_player,bet,rounds,final\ndata,1,1,1\n"
  },
  {
    "path": "environment.yaml",
    "chars": 1963,
    "preview": "name: mastering\nchannels:\n  - defaults\ndependencies:\n  - alabaster=0.7.12=py37_0\n  - asn1crypto=0.24.0=py37_0\n  - astroi"
  },
  {
    "path": "requirements.txt",
    "chars": 1732,
    "preview": "# This file may be used to create an environment using:\n# $ conda create --name <env> --file <this file>\n# platform: osx"
  },
  {
    "path": "show_hierarchies.py",
    "chars": 890,
    "preview": "\"\"\"\nCreate ASCII Art for hierarchies\n\nUses asciitree. https://pypi.org/project/asciitree/0.3.3/\n\"\"\"\n\nfrom asciitree impo"
  },
  {
    "path": "stubs/sqlite3.pyi",
    "chars": 597,
    "preview": "\"\"\"A start of a stub file to correct the error in the current sqlite3 definition.\"\"\"\n\nfrom typing import Union, Type, Op"
  },
  {
    "path": "test_all.py",
    "chars": 5504,
    "preview": "#!/usr/bin/env python3\n\"\"\"Run all the chapter modules, doctests or performance() function\n\nThis is run from the top-leve"
  },
  {
    "path": "tox.ini",
    "chars": 3332,
    "preview": "[tox]\nskipsdist = True\nenvlist = py37\n\n[testenv]\ndeps =\n    pytest\n    flask\n    requests\n    jinja2\n    sqlalchemy\n    "
  }
]

About this extraction

This page contains the full source code of the PacktPublishing/Mastering-Object-Oriented-Python-Second-Edition GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 142 files (582.5 KB), approximately 165.3k tokens, and a symbol index with 1344 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!