# packages

pkgId: 1d840401f43367d07e63059807d2378b3836e3e4b9f2953acb883bb89a73736b
name: python-bicleaner-ai
arch: src
version: 3.1.0
epoch: 0
release: 1
url: https://pypi.org/project/bicleaner-ai/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bicleaner-ai-3.1.0-1.src.rpm
checksum_type: sha256
summary:
  Parallel corpus classifier, indicating the likelihood of a pair of sentences being mutual translations or not (neural version)

description:
  ![License](https://img.shields.io/badge/License-GPLv3-blue.svg)

  Bicleaner AI (`bicleaner-ai-classify`) is a tool in Python that aims at detecting noisy sentence pairs in a parallel corpus. It
  indicates the likelihood of a pair of sentences being mutual translations (with a value near to 1) or not (with a value near to 0).
  Sentence pairs considered very noisy are scored with 0.

  Although a training tool (`bicleaner-ai-train`) is provided, you may want to use the available ready-to-use language packages.
  Please, use `bicleaner-ai-download` to download the latest language packages or visit the [Github releases](https://github.com/bitextor/bicleaner-ai-data/releases/latest) for lite models and [Hugging Face Hub](https://huggingface.co/bitextor) for full models since v2.0.
  Visit our [docs](docs/TRAINING.md) for a detailed example on Bicleaner training.

  If you find Bicleaner AI useful, please consider [citing us](#citation).



  New improved [multilingual models](#multilingual-models) for zero-shot classification.

  <details>
  <summary>Previous news</summary>


  Model accuracy improvements and HF integration! See [CHANGELOG](https://github.com/bitextor/bicleaner-ai/blob/v2.0/CHANGELOG.md).


  Bicleaner AI is a [Bicleaner](https://github.com/bitextor/bicleaner) fork that uses neural networks.
  It comes with two types of models, lite models for fast scoring and full models for high performance.
  Lite models use [A Decomposable Attention Model for Natural Language Inference (Parikh et al.)](https://arxiv.org/abs/1606.01933).
  Full models use fine-tuned XLMRoberta ([Unsupervised Cross-lingual Representation Learning at Scale](https://arxiv.org/abs/1911.02116)).

  The use of XLMRoberta and 1:10 positive to negative ratio were inspired in the winner of WMT20 Parallel Corpus Filtering Task paper ([Filtering noisy parallel corpus using transformers with proxy task learning](https://www.statmt.org/wmt20/pdf/2020.wmt-1.105.pdf)).

  </details>


  - Python >= 3.8
  - PIP >= 23.0
  - CUDA >=11.2 (for training and inference with full models)

  Bicleaner AI is written in Python and can be installed using `pip`.
  It also requires the [KenLM](https://github.com/kpu/kenlm) Python bindings with support for 7-gram language models.
  Hardrules uses [FastSpell](https://github.com/mbanon/fastspell) that requires `cyhunspell` to be installed manually.
  You can easily install all the requirements by running the following commands:

  ```bash
  pip install bicleaner-ai git+https://github.com/MSeal/cython_hunspell@2.0.3
  pip install --config-settings="--build-option=--max_order=7" https://github.com/kpu/kenlm/archive/master.zip
  ```

  After installation, three binary files (`bicleaner-ai-train`, `bicleaner-ai-classify`, `bicleaner-ai-download`) will be located in your `python/installation/prefix/bin` directory. This is usually `$HOME/.local/bin` or `/usr/local/bin/`.


  TensorFlow 2 will be installed as a dependency and GPU support is required for training.
  `pip` will install latest TensorFlow supported version, but older versions `>=2.6.5` are supported and can be installed if your machine does not meet TensorFlow CUDA requirements.
  See [this](https://www.tensorflow.org/install/source#gpu) table for the CUDA and TensorFlow versions compatibility.
  In case you want a different TensorFlow version, you can downgrade using:
  ```bash
  pip install tensorflow==2.6.5
  ```

  TensorFlow logging messages are suppressed by default, in case you want to see them you have to explicitly set `TF_CPP_MIN_LOG_LEVEL` environment variable.
  For example:
  ```bash
  TF_CPP_MIN_LOG_LEVEL=0 bicleaner-ai-classify
  ```
  **WARNING**: If you are experiencing slow downs because Bicleaner AI is not running in the GPU, you should check those logs to see if TensorFlow is loading all the libraries correctly.



  For Serbo-Croatian languages, models work better with transliteration. To be able score transliterated text, install optional dependency:
  ```
  pip install bicleaner-ai[transliterate]
  ```
  Note that this won't transliterate the output text, it will be used only for scoring.




  `bicleaner-ai-classify` aims at detecting noisy sentence pairs in a parallel corpus. It
  indicates the likelihood of a pair of sentences being mutual translations (with a value near to 1) or not (with a value near to 0). Sentence pairs considered very noisy are scored with 0.

  By default, the input file (the parallel corpus to be classified) expects at least four columns, being:

  * col1: URL 1
  * col2: URL 2
  * col3: Source sentence
  * col4: Target sentence

  but the source and target sentences column index can be customized by using the `--scol` and `--tcol` flags. Urls are not mandatory.

  The generated output file will contain the same lines and columns that the original input file had, adding an extra column containing the Bicleaner AI classifier score.


  Bicleaner AI has two types of models, full and lite models.
  Full models are recommended, as they provide much higher quality.
  If speed is a hard constraint to you, lite models could be an option (take a look at the speed [comparison](#speed)).

  See available full models [here](https://huggingface.co/models?other=bicleaner-ai) and available lite models [here](https://github.com/bitextor/bicleaner-ai-data/releases/latest).

  You can download the model with:
  ```
  bicleaner-ai-download en fr full
  ```
  This will download `bitextor/bicleaner-ai-full-en-fr` model from HuggingFace and store it at the cache directory.

  Or you can download a lite model with:
  ```
  bicleaner-ai-download en fr lite ./bicleaner-models
  ```
  This will download and store the en-fr lite model at `./bicleaner-models/en-fr`.

  Since 2.3.0 version, full models also accept a local path to download, instead of the HF cache directory.
  In that case, to use the model, provide the local path instead of the HF identifier.

  To read more information about how HF cache works, please read the [official documentation](https://huggingface.co/docs/transformers/v4.30.0/en/installation#cache-setup).


  To classify a tab separated file containing English sentences in the first column and French sentences in the second column, use
  ```bash
  bicleaner-ai-classify  \
          --scol 1 --tcol 2
          corpus.en-fr.tsv  \
          corpus.en-fr.classifed.tsv  \
          bitextor/bicleaner-ai-full-en-fr
  ```
  where `--scol` and `--tcol` indicate the location of source and target sentence,
  `corpus.en-fr.tsv` the input file,
  `corpus.en-fr.classified.tsv` output file and `bitextor/bicleaner-ai-en-fr` is the HuggingFace model name.
  Each line of the new file will contain the same content as the input file, adding a column with the score given by the Bicleaner AI classifier.

  Note that, to use a lite model, you need to provide model path in your local file system, instead of HuggingFace model name.



  There are multilingual full models [available](https://huggingface.co/models?other=bicleaner-ai&search=xx).
  They can work with, potentially, any language (currently only paired with English) that XLMR [supports](https://github.com/facebookresearch/fairseq/tree/main/examples/xlmr#introduction).
  To see a further explaination on how to train a multilingual model or how our models perform, take a look [here](docs/training/multilingual.md) and [here](docs/training/multilingual.md#performance).

  **WARNING**: multilingual models will disable hardrules that expect language parameter.
  You can, however, overwrite the language code in the model configuration with `-s`/`--source_lang` or `-t`/`--target_lang` options during classify. For example when scoring English-Icelandic data, use:
  ```
  bicleaner-ai-classify \
      --scol 1 --tcol 2 \
      -t is \
      corpus.en-is.tsv \
      corpus.en-is.classified.tsv \
      bitextor/bicleaner-ai-full-en-xx
  ```



  <details>
  <summary>Full description of the command-line parameters:</summary>

  ```
  usage: bicleaner-ai-classify [-h] [-s SOURCE_LANG] [-t TARGET_LANG] [-S SOURCE_TOKENIZER_COMMAND] [-T TARGET_TOKENIZER_COMMAND] [--header] [--scol SCOL] [--tcol TCOL] [-b BLOCK_SIZE] [-p PROCESSES] [--batch_size BATCH_SIZE]
                               [--tmp_dir TMP_DIR] [--score_only] [--calibrated] [--raw_output] [--lm_threshold LM_THRESHOLD] [--disable_hardrules] [--disable_lm_filter] [--disable_porn_removal] [--disable_minimal_length]
                               [--run_all_rules] [--rules_config RULES_CONFIG] [--offline] [--auth_token AUTH_TOKEN] [-q] [--debug] [--logfile LOGFILE] [-v]
                               input [output] model

  positional arguments:
    input                 Tab-separated files to be classified
    output                Output of the classification (default: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>)
    model                 Path to model directory or HuggingFace Hub model identifier (such as 'bitextor/bicleaner-ai-full-en-fr')

  options:
    -h, --help            show this help message and exit

  Optional:
    -s SOURCE_LANG, --source_lang SOURCE_LANG
                          Overwrite model config source language (default: None)
    -t TARGET_LANG, --target_lang TARGET_LANG
                          Overwrite model config target language (default: None)
    -S SOURCE_TOKENIZER_COMMAND, --source_tokenizer_command SOURCE_TOKENIZER_COMMAND
                          Source language (SL) tokenizer full command (default: None)
    -T TARGET_TOKENIZER_COMMAND, --target_tokenizer_command TARGET_TOKENIZER_COMMAND
                          Target language (TL) tokenizer full command (default: None)
    --header              Input file will be expected to have a header, and the output will have a header as well (default: False)
    --scol SCOL           Source sentence column (starting in 1). The name of the field is expected instead of the position if --header is set (default: 3)
    --tcol TCOL           Target sentence column (starting in 1). The name of the field is expected instead of the position if --header is set (default: 4)
    -b BLOCK_SIZE, --block_size BLOCK_SIZE
                          Sentence pairs per block (default: 10000)
    -p PROCESSES, --processes PROCESSES
                          Option no longer available, please set BICLEANER_AI_THREADS environment variable (default: None)
    --batch_size BATCH_SIZE
                          Sentence pairs per block (default: 32)
    --tmp_dir TMP_DIR     Temporary directory where creating the temporary files of this program (default: /tmp)
    --score_only          Only output one column which is the bicleaner score (default: False)
    --calibrated          Output calibrated scores (default: False)
    --raw_output          Return raw output without computing positive class probability. (default: False)
    --lm_threshold LM_THRESHOLD
                          Threshold for language model fluency scoring. All TUs whose LM fluency score falls below the threshold will are removed (classifier score set to 0), unless the option --keep_lm_result set. (default: 0.5)
    --disable_hardrules   Disables the bicleaner_hardrules filtering (only bicleaner_classify is applied) (default: False)
    --disable_lm_filter   Disables LM filtering (default: False)
    --disable_porn_removal
                          Don't apply porn removal (default: False)
    --disable_minimal_length
                          Don't apply minimal length rule (default: False)
    --run_all_rules       Run all rules of Hardrules instead of stopping at first discard (default: False)
    --rules_config RULES_CONFIG
                          Hardrules configuration file (default: None)
    --offline             Don't try to download the model, instead try directly to load from local storage (default: False)
    --auth_token AUTH_TOKEN
                          Auth token for the Hugging Face Hub (default: None)

  Logging:
    -q, --quiet           Silent logging mode (default: False)
    --debug               Debug logging mode (default: False)
    --logfile LOGFILE     Store log to a file (default: <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>)
    -v, --version         Show version of the package and exit
  ```

  </details>


  Bicleaner AI provides a command-line tool to train your own model, in case available models do not fit your needs.
  Please go to our [training documentation](docs/training) for a quick start and further details.


  To set the maximum number of threads/processes to be used during training or classifying, `--processes` option is no longer available.
  You will need to set `BICLEANER_AI_THREADS` environment variable to the desired value.
  For example:
  ```
  BICLEANER_AI_THREADS=12 bicleaner-ai-classify ...
  ```
  If the variable is not set, the program will use all the available CPU cores.


  A comparison of the speed in number of sentences per second between different types of models and hardware:

  | model | speed CPUx1 | speed GPUx1 |
  | ----- | ----------- | ----------- |
  | full | 1.78 rows/sec | 200 rows/sec |
  | lite | 600 rows/sec | 10,000 rows/sec |

  * CPU: Intel Core i9-9960X single core (lite model batch 16, full model batch 1)
  * GPU: Nvidia V100 (lite model batch 2048, full model batch 16)



  > J. Zaragoza-Bernabeu, M. Bañón, G. Ramírez-Sánchez, S. Ortiz-Rojas, \
  > "[Bicleaner AI: Bicleaner Goes Neural](https://aclanthology.org/2022.lrec-1.87/)", \
  > in *Proceedings of the 13th Language Resources and Evaluation Conference*. \
  > Marseille, France: Language Resources and Evaluation Conference, June 2022

  ```latex
  @inproceedings{zaragoza-bernabeu-etal-2022-bicleaner,
      title = {"Bicleaner {AI}: Bicleaner Goes Neural"},
      author = {"Zaragoza-Bernabeu, Jaume  and
        Ram{\'\i}rez-S{\'a}nchez, Gema  and
        Ba{\~n}{\'o}n, Marta  and
        Ortiz Rojas, Sergio"},
      booktitle = {"Proceedings of the Thirteenth Language Resources and Evaluation Conference"},
      month = jun,
      year = {"2022"},
      address = {"Marseille, France"},
      publisher = {"European Language Resources Association"},
      url = {"https://aclanthology.org/2022.lrec-1.87"},
      pages = {"824--831"},
      abstract = {"This paper describes the experiments carried out during the development of the latest version of Bicleaner, named Bicleaner AI, a tool that aims at detecting noisy sentences in parallel corpora. The tool, which now implements a new neural classifier, uses state-of-the-art techniques based on pre-trained transformer-based language models fine-tuned on a binary classification task. After that, parallel corpus filtering is performed, discarding the sentences that have lower probability of being mutual translations. Our experiments, based on the training of neural machine translation (NMT) with corpora filtered using Bicleaner AI for two different scenarios, show significant improvements in translation quality compared to the previous version of the tool which implemented a classifier based on Extremely Randomized Trees."},
  }
  ```

  ___

  ![Connecting Europe Facility](https://www.paracrawl.eu/images/logo_en_cef273x39.png)

  All documents and software contained in this repository reflect only the authors' view. The Innovation and Networks Executive Agency of the European Union is not responsible for any use that may be made of the information it contains.

rpm_license:
  GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.  Preamble  The GNU General Public License is a free, copyleft license for software and other kinds of works.  The licenses for most software and other practical works are designed to take away your freedom to share and change the works.  By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.  We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors.  You can apply it to your programs, too.  When we speak of free software, we are referring to freedom, not price.  Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.  To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights.  Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.  For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received.  You must make sure that they, too, receive or can get the source code.  And you must show them these terms so they know their rights.  Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.  For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software.  For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.  Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so.  This is fundamentally incompatible with the aim of protecting users' freedom to change the software.  The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable.  Therefore, we have designed this version of the GPL to prohibit the practice for those products.  If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.  Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary.  To prevent this, the GPL assures that patents cannot be used to render the program non-free.  The precise terms and conditions for copying, distribution and modification follow.  TERMS AND CONDITIONS  0. Definitions.  "This License" refers to version 3 of the GNU General Public License.  "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.  "The Program" refers to any copyrightable work licensed under this License.  Each licensee is addressed as "you".  "Licensees" and "recipients" may be individuals or organizations.  To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy.  The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.  A "covered work" means either the unmodified Program or a work based on the Program.  To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy.  Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.  To "convey" a work means any kind of propagation that enables other parties to make or receive copies.  Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.  An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License.  If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.  1. Source Code.  The "source code" for a work means the preferred form of the work for making modifications to it.  "Object code" means any non-source form of a work.  A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.  The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form.  A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.  The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities.  However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work.  For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.  The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.  The Corresponding Source for a work in source code form is that same work.  2. Basic Permissions.  All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met.  This License explicitly affirms your unlimited permission to run the unmodified Program.  The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work.  This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.  You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force.  You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright.  Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.  Conveying under any other circumstances is permitted solely under the conditions stated below.  Sublicensing is not allowed; section 10 makes it unnecessary.  3. Protecting Users' Legal Rights From Anti-Circumvention Law.  No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.  When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.  4. Conveying Verbatim Copies.  You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.  You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.  5. Conveying Modified Source Versions.  You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:  a) The work must carry prominent notices stating that you modified it, and giving a relevant date.  b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7.  This requirement modifies the requirement in section 4 to "keep intact all notices".  c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy.  This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged.  This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.  d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.  A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit.  Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.  6. Conveying Non-Source Forms.  You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:  a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.  b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.  c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source.  This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.  d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge.  You need not require recipients to copy the Corresponding Source along with the object code.  If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source.  Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.  e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.  A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.  A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling.  In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage.  For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product.  A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.  "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source.  The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.  If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information.  But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).  The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed.  Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.  Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.  7. Additional Terms.  "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law.  If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.  When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it.  (Additional permissions may be written to require their own removal in certain cases when you modify the work.)  You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.  Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:  a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or  b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or  c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or  d) Limiting the use for publicity purposes of names of licensors or authors of the material; or  e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or  f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.  All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10.  If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term.  If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.  If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.  Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.  8. Termination.  You may not propagate or modify a covered work except as expressly provided under this License.  Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).  However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.  Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.  Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License.  If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.  9. Acceptance Not Required for Having Copies.  You are not required to accept this License in order to receive or run a copy of the Program.  Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance.  However, nothing other than this License grants you permission to propagate or modify any covered work.  These actions infringe copyright if you do not accept this License.  Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.  10. Automatic Licensing of Downstream Recipients.  Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License.  You are not responsible for enforcing compliance by third parties with this License.  An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations.  If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.  You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License.  For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.  11. Patents.  A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based.  The work thus licensed is called the contributor's "contributor version".  A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version.  For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.  Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.  In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement).  To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.  If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients.  "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.  If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.  A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License.  You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.  Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.  12. No Surrender of Others' Freedom.  If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License.  If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all.  For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.  13. Use with the GNU Affero General Public License.  Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work.  The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.  14. Revised Versions of this License.  The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time.  Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.  Each version is given a distinguishing version number.  If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation.  If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.  If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.  Later license versions may give you additional or different permissions.  However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.  15. Disclaimer of Warranty.  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.  16. Limitation of Liability.  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  17. Interpretation of Sections 15 and 16.  If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.  END OF TERMS AND CONDITIONS  How to Apply These Terms to Your New Programs  If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.  To do so, attach the following notices to the program.  It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.  <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year>  <name of author>  This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.  You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.  Also add information on how to contact you by electronic and paper mail.  If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:  <program>  Copyright (C) <year>  <name of author> This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.  The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License.  Of course, your program's commands might be different; for a GUI interface, you would use an "about box".  You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.  The GNU General Public License does not permit incorporating your program into proprietary programs.  If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library.  If this is what you want to do, use the GNU Lesser General Public License instead of this License.  But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.


pkgId: ee0e92fa457d342dccf3ca5f9a7cbad56df2442b39495b328fd730b13aca06fc
name: python-bicon
arch: src
version: 1.3.4
epoch: 0
release: 1
summary: BiCoN - a package for network-constrained biclustering of omics data
url: https://github.com/biomedbigdata/BiCoN
rpm_license: GNU General Public License v3 (GPLv3)
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bicon-1.3.4-1.src.rpm
checksum_type: sha256
description:
  * [General info](#general-info)
  * [Installation](#installation)
  * [Data input](#data-input)
  * [Main functions](#main-functions)
  * [Example](#example)
  * [Quality control](#quality-control)
  * [Cite](#cite)
  * [Contact](#contact)



  Unsupervised learning approaches are frequently employed to identify patient subgroups and biomarkers such as disease-associated genes. Biclustering is a powerful technique often used with expression data to cluster genes along with patients. However, the genes forming biclusters are often not functionally related, complicating interpretation of the results.

  To alleviate this, we developed the network-constrained biclustering approach BiCoN which **(i)** restricts biclusters to functionally related genes connected in molecular interaction networks and **(ii)** maximizes the expression difference between two subgroups of patients.

  ![alt text](https://github.com/biomedbigdata/BiCoN/blob/master/img/schema.png?raw=true)




  To install the package from PyPI please run:

  `pip install bicon`

  To install the package from git:

  `git clone https://github.com/biomedbigdata/BiCoN`

  `python setup.py install`




  The algorithm needs as an input a matrix with gene expression/methylation/any other numerical data and one file with a network.

  Any gene IDs can be used (see results processing)



  Numerical data is accepted in the following format:
  - genes as rows.
  - patients as columns.
  - first column - genes IDs (can be any IDs).

  For instance:

  | Unnamed: 0 | GSM748056 | GSM748059 | ... | GSM748278 | GSM748279 | GSM1465989 |
  |------------|-----------|-----------|-----|-----------|-----------|------------|
  | 1454       | 0.053769  | 0.117412  | ... | -0.392363 | -1.870838 | -1.432554  |
  | 201931     | -0.618279 | 0.278637  | ... | 0.803541  | -0.514947 | 2.361925   |
  | 8761       | 0.215820  | -0.343865 | ... | 0.700430  | 0.073281  | -0.977656  |
  | 2703       | -0.504701 | 1.295049  | ... | 1.861972  | 0.601808  | 0.191013   |
  | 26207      | -0.626415 | -0.646977 | ... | 2.331724  | 2.339122  | -0.100924  |

  There are 2 examples of gene expression datasets that can be placed in the "data" folder
  - GSE30219 - a Non-Small Cell Lung Cancer dataset from GEO for patients with either adenocarcinoma or squamous cell carcinoma.
  - TCGA pan-cancer dataset with patients that have luminal or basal breast cancer.
  Both can be found [here](https://drive.google.com/drive/folders/1J0XRrklwcV_Cgy_9Ay_6yJrN_x28Cosk?usp=sharing)



  An interaction network should be present as a table with two columns that represent two interacting genes. **Without a header!**

  For instance:

  | 6416 | 2318 |
  |------|------|
  | 6416 | 5371 |
  | 6416 | 351  |
  | 6416 | 409  |
  | 6416 | 5932 |
  | 6416 | 1956 |

  There is an example of a PPI network from BioiGRID with experimentally validated interactions [here](https://drive.google.com/drive/folders/1J0XRrklwcV_Cgy_9Ay_6yJrN_x28Cosk?usp=sharing).



  Here we give a general description of the main functions provided. Please note, that all functions are annotated with dockstrings and therefore the full information can be found with *help()* method, e.g. `help(results.save)`.

  1.**data_preprocessing**(path_expr, path_net, log2 = False, zscores = True, size = 2000, no_zero = None, formats = [])

  Parameters:

  - path_to_expr: *string*, path to the numerical data
  - path_to_net: *string*, path to the network file
  - log2: *bool, (default = False)*, indicates if log2 transformation should be applied to the data
  - size: *int, optional (default = 2000)* determines the number of genes that should be pre-selected by variance for the analysis. Shouldn't be higher than 5000.
  - no_zero: (default - none) indicate the fraction of allowed non-zero values for each patient. For instance no_zero = 0.8 means that all genes which have no expression for at least 80% of patients will be removed from the analysis
  - formats: *list* (defailt - determined automatically), list of formats for gene expression matrix and PPI table. It is advisable to provide formats to avoid mistakes in files reading. For example, if the gene expression matrix is given in .csv format and PPI in .tsv format, then format = ["csv", "tsv"]

  Returns:

  - GE: *pandas data frame*, processed expression data
  - G: *networkX graph*, processed network data
  - labels: *dict*, for mapping between real genes/patients IDs and the internal ones
  - rev_labels: *dict*, additional dictionary for mapping between real genes/patients IDs and the internal ones

  2. *BiCoN**(GE,G,L_g_min,L_g_max) creates a model for the given data:

  Parameters:

  - GE: *pandas dataframe*, processed expression data
  - G: *networkX graph*, processed network data
  - L_g_min: *int*, minimal solution subnetwork size
  - L_g_max: *int*, maximal solution subnetwork size

  Methods:

  BiCoN.**run**(self, n_proc = 1, K = 20, evaporation = 0.5, show_plot = False)

  - K: *int, default = 20*, number of ants. Fewer ants - less space exploration. Usually set between 20 and 50
  - n_proc: *int, default = 1*, number of processes that should be used(can not be more than K)
  - evaporation, *float, default = 0.5*, the rate at which pheromone evaporates
  - show_plot: *bool, default = False*, set true if convergence plots should be shown during the iterations



  Import the package:

  ```python
  from bicon import data_preprocessing
  from bicon import BiCoN
  from bicon import results_analysis
  ```
  Set the paths to the expression matrix and the PPI network:

  ```python
  path_expr,path_net ='/data/gse30219_lung.csv', '/data/biogrid.human.entrez.tsv'
  ```
  Load and process the data:

  ```python
  GE,G,labels, _= data_preprocessing(path_expr, path_net)
  ```
  Set the size of subnetworks:
  ```python
  L_g_min = 10
  L_g_max = 15
  ```
  Set the model and run the search:

  ```python
  model = BiCoN(GE,G,L_g_min,L_g_max)
  solution,scores= model.run_search()
  ```

  BiCoN package also allows a user to save the results and perform an initial analysis.
  The examples below show the basic usage, for more details please use python help() method, e.g. `help(results.save)`.

  1. First, the object for results analysis must be created:
  ```python
  results = results_analysis(solution, labels)
  ```
  This will allow to easily access the resulting biclusters and their initial IDs as well as perform a more complicated analysis.

  To access IDs of patients in the first bicluster:
  ```python
  results.patients1
  ```
  To access IDs of genes IDs in the first bicluster:
  ```python
  results.genes1
  ```
  Same logic applies to the second bicluster.

  If in the further analysis you would like to use gene names, please set 'convert' to True and specify the original gene IDs, i.e.:
  ```python
  results = results_analysis(solution, labels, convert = True, origID = 'entrezgene')
  ```
  Some other options for the original gene ID: ensembl.gene', 'symbol', 'refseq', 'unigene', etc
  For all possibe option please check  the reference for MyGene.info gene query [web service](            http://docs.mygene.info/en/latest/doc/query_service.html#available_fields)

  2. To save the solution:

  ```python
  results.save(output = "results/results.csv")
  ```

  3. Visualise the resulting networks colored with respect to their difference in expression patterns in patients clusters:
  ```python
  results.show_networks(GE, G, output = "results/network.png")
  ```

  4. Visualise a clustermap of the achieved solution alone or also along with the known patients' groups.
  Just with the BiCoN results:

  ```python
  results.show_clustermap(GE, G, output = "results/clustermap.png")
  ```
  If you have a patient's phenotype you would like to use for comparison, please make sure that patients IDs are exactly (!) matching the IDs that were used as an input. The IDs should be represented as a list of two lists, e.g.:

  ```python
  true_classes = [['GSM748056', 'GSM748059',..], ['GSM748278', 'GSM748279', 'GSM1465989']]
  results.show_clustermap(GE, G, output = "results/clustermap.png", true_labels = true_classes)
  ```

  5. Given a known phenotype in a format described above, BiCoN can also return Jaccard index of the achieved patients clustering with a given phenotype:

  ```python
  results.jaccard_index(true_labels = true_classes)
  ```

  6. BiCoN is using [gseapy](https://gseapy.readthedocs.io/en/master/index.html) module to provide a user with a python wrapper for [Enrichr](https://amp.pharm.mssm.edu/Enrichr/) database.

  ```python
  results.enrichment_analysis(library = 'GO_Biological_Process_2018', output="results")
  ```

  After the execution of the given above code, in the */results* directory a user can find a table with enriched pathways as well as enrichment plots. Other available libraries can be used as well, e.g. 'GO_Molecular_Function_2018' and 'GO_Cellular_Component_2018'. In total there are 159 libraries available at the moment and the full list can be found by typing:

  ```python
  import gseapy
  gseapy.get_library_name()
  ```



  The best way to check if the algorithm produced high-quality results and there are no issues with the parameters is to analyse the convergence plot:

  ```python
  results.convergence_plot(scores)
  ```
  * The algorithm converged:

  1. If the maximum score has stabilised for several iterations in a row (default is 6).

  OR

  2. If the average score became equal (or nearly equal) to the maximal score:

  ![text](https://github.com/biomedbigdata/BiCoN/blob/master/img/conv1.png?raw=true)

  * The algorithm did not converge:

  If the average and the maximal score improve over the iterations but do not stabilize then just increase the number of maximally allowed iterations:

  ![text](https://github.com/biomedbigdata/BiCoN/blob/master/img/conv2.png?raw=true)

  If the scores do not stabilize even after 60-100 iterations, please contact us.


  If you got the following error message:
  ```python
  AssertionError: bad probability update
  ```
  It can mean one of the following issues:
  1. The setting of the algorithm is way to restrictive for your problem. You can try to fix it by repeating the analysis with th = 0, or even th = -1 e.g.:

  ```python
  model = BiCoN(GE,G,L_g_min,L_g_max)
  solution,scores= model.run_search(th = 0)

  ```
  2. Otherwise, the problem might be related to the way you have processed your data. Please make sure that you do not have not expressed genes for the magority of the patients.



  BiCoN was developed by the [Big Data in BioMedicine group](biomedical-big-data.de) and [Computational Systems Medicine group](https://compsysmed.de/) at [Chair of Experimental Bioinformatics](https://www.baumbachlab.net/).

  If you use BiCoN in your research, we kindly ask you to cite the following manuscript:
  ` Lazareva, O., Van Do, H., Canzar, S., Yuan, K., Tieri, P.,Baumbach, J., Kacprowski, T., List, M.: BiCoN: Network-constrained biclustering of patients and omics data. [Submitted]`


  If you have difficulties using BiCoN, please open an issue at out [GitHub](https://github.com/biomedbigdata/BiCoN) repository. Alternatevely, you can write an email to:
  * [Olga Lazareva](mailto:olga.lazareva@wzw.tum.de?subject=[BiCoN-PyPI]%20BiCoNs%20WEB)
  * [Markus List](mailto:markus.list@wzw.tum.de?subject=[BiCoN-PyPI]%20BiCoN%20WEB)
  * [Tim Kacprowski](mailto:tim.kacprowski@wzw.tum.de?subject=[BiCoN-PyPI]%20BiCoNs%20WEB)


pkgId: 469f6cd6fc5029da268b7d94364a66a45143fd76ecd17740cc7ae588af438cd0
name: python-bidly
arch: src
version: 0.6
epoch: 0
release: 1
summary: A bidding logic library for Django applications
url: https://github.com/Priya-1110/bidly
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bidly-0.6-1.src.rpm
checksum_type: sha256
description:
                                                                   ____  ____  ____  __   _  _
                                                                  (  _ \(_  _)(  _ \(  ) ( \/ )
                                                                   ) _ < _)(_  )(_) ))(__ \  /
                                                                  (____/(____)(____/(____)(__)



  **Bidly** is a lightweight library for managing auction-style bidding logic. It simplifies the process of handling bids, minimum increments, and tracking the highest bids, making it perfect for Django-based projects.

  ** Features **

  - **Flexible Bidding Logic**: Supports starting bids, bid increments, and highest bid tracking tailored for Django applications.
  - **Easy Integration with Django**: Seamlessly integrates into any Django project with minimal configuration.
  - **Model Support**: Works with Django models to manage bids and auctions efficiently.

  **Installation** :

  pip install bidly


pkgId: e15568249382eacfe913c541144761495e575b3ebcc577f1d22cb2124bbb08aa
name: python-bidplotter
arch: src
version: 0.6
epoch: 0
release: 1
summary: A library for predicting auction prices and visualizing bidding progress.
url: https://github.com/Priya-1110/bidplotter
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bidplotter-0.6-1.src.rpm
checksum_type: sha256
description:
                                                      __________.__    .___      .__          __    __
                                                      \______   \__| __| _/_____ |  |   _____/  |__/  |_  ___________
                                                       |    |  _/  |/ __ |\____ \|  |  /  _ \   __\   __\/ __ \_  __ \
                                                       |    |   \  / /_/ ||  |_> >  |_(  <_> )  |  |  | \  ___/|  | \/
                                                       |______  /__\____ ||   __/|____/\____/|__|  |__|  \___  >__|
                                                              \/        \/|__|                               \/



  **BidPlotter** is a Python-based library that calculates predicted final auction prices, simulates bidding progression, and generates real-time bidding progression visualizations. It’s a perfect tool for auction platforms and enthusiasts to analyze and predict bidding outcomes effectively.

  **Key Features **:

  Predict the final auction price based on starting price, bid increment, and auction duration.
  Simulate bidding activity over a specified timeframe.
  Generate and save bidding progression visualizations.
  Lightweight and easy to integrate into Python applications.

  **Installation** :

  pip install bidplotter


pkgId: 4e35a533f228b80f56d38d1daec8b2a13456c08445e5e0d56d9811a55557124f
name: python-bids2table
arch: src
version: 0.1.0
epoch: 0
release: 1
summary: Efficiently index large-scale BIDS datasets and derivatives
url: https://github.com/childmindresearch/bids2table
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bids2table-0.1.0-1.src.rpm
checksum_type: sha256
description:
  [![Build](https://github.com/childmindresearch/bids2table/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/childmindresearch/bids2table/actions/workflows/ci.yaml?query=branch%3Amain)
  [![Docs](https://github.com/childmindresearch/bids2table/actions/workflows/docs.yaml/badge.svg?branch=main)](https://childmindresearch.github.io/bids2table/bids2table)
  [![codecov](https://codecov.io/gh/childmindresearch/bids2table/branch/main/graph/badge.svg?token=22HWWFWPW5)](https://codecov.io/gh/childmindresearch/bids2table)
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

  bids2table is a library for efficiently indexing and querying large-scale BIDS neuroimaging datasets and derivatives. It aims to improve upon the efficiency of [PyBIDS](https://github.com/bids-standard/pybids) by leveraging modern data science tools.

  bids2table represents a BIDS dataset index as a single table with columns for BIDS entities and file metadata. The index is constructed using [Arrow](https://arrow.apache.org/) and stored in [Parquet](https://parquet.apache.org/) format, a binary tabular file format optimized for efficient storage and retrieval.



  A pre-release version of bids2table can be installed with

  ```sh
  pip install bids2table
  ```

  The latest development version can be installed with

  ```sh
  pip install git+https://github.com/childmindresearch/bids2table.git
  ```



  Our documentation is [here](https://childmindresearch.github.io/bids2table/).



  ```python
  import pandas as pd

  from bids2table import bids2table


  df = bids2table("/path/to/dataset")


  df = bids2table("/path/to/dataset", persistent=True, workers=8)
  ```

  See [here](example/example.ipynb) for a more complete example.



  bids2table significantly outperforms both [PyBIDS](https://github.com/bids-standard/pybids) and [ancpBIDS](https://github.com/ANCPLabOldenburg/ancp-bids) in terms of indexing run time, index size on disk, and query run time.



  Indexing run time and index size on disk for the [NKI Rockland Sample](https://fcon_1000.projects.nitrc.org/indi/pro/nki.html) dataset. See the [indexing benchmark](benchmark/indexing) for more details.

  | Index | Num workers | Run time (s) | Index size (MB) |
  | -- | -- | -- | -- |
  | PyBIDS | 1 | 1618 | 448 |
  | ancpBIDS | 1 | 465 | -- |
  | bids2table | 1 | 402 | 4.02 |
  | bids2table | 8 | 53.2 | **3.84** |
  | bids2table | 64 | **10.7** | 4.82 |




  Query run times for the [Chinese Color Nest Project](http://deepneuro.bnu.edu.cn/?p=163) dataset. See the [query benchmark](benchmark/query) for more details.

  | Index | Get subjects (ms) | Get BOLD (ms) | Query metadata (ms) | Get morning scans (ms) |
  | -- | -- | -- | -- | -- |
  | PyBIDS | 1350 | 12.3 | 6.53 | 34.3 |
  | ancpBIDS | 30.6 | 19.2 | -- | -- |
  | bids2table | **0.046** | **0.346** | **0.312** | **0.352** |


pkgId: 966afbb371106db696b9e72cd0233a43d673d15f6d7c68e796abb029ed238fd4
name: python-bidskit
arch: src
version: 2024.11.22
epoch: 0
release: 1
summary: BIDS curation toolkit
url: https://github.com/jmtyszka/bidskit
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bidskit-2024.11.22-1.src.rpm
checksum_type: sha256
description:
  Python utilities for converting from DICOM to BIDS neuroimaging formats.

  The *bidskit* console command takes a directory tree containing imaging series from one or more subjects (eg T1w MPRAGE, BOLD EPI, Fieldmaps), converts the imaging data to Nifti-1 format with JSON metadata files (sidecars) and populates a directory tree according to the latest BIDS specification.








  Let us know about bugs and feature requests through this repo's
  [GitHub Issues](https://github.com/jmtyszka/bidskit/issues) page.


  - [heudiconv](https://github.com/nipy/heudiconv) General purpose heuristic DICOM converter
  - [dcm2bids](https://github.com/cbedetti/Dcm2Bids) Christophe Beddetti's DICOM to BIDS converter


pkgId: e020ca669d426ec0cbc883d917db80ebb3f015dc895f2bd392294ed5aba14896
name: python-bidsphysio
arch: src
version: 21.6.24
epoch: 0
release: 1
summary: Physio-to-BIDS Converter
url: https://pypi.org/project/bidsphysio/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bidsphysio-21.6.24-1.src.rpm
checksum_type: sha256
description:
  Converts physio data from either CMRR DICOM, CMRR log, Siemens PMU
  or AcqKnowledge file to BIDS physiological recording.


pkgId: 8f5c820c7d00b099251d950ba61ba31bb7bbcdb34d3ca52763236a7626be8683
name: python-bidsschematools
arch: src
version: 0.11.3.post1
epoch: 0
release: 1
summary: Python tools for working with the BIDS schema.
url: https://github.com/bids-standard/bids-specification
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bidsschematools-0.11.3.post1-1.src.rpm
checksum_type: sha256
description:
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
  [![codecov](https://codecov.io/gh/bids-standard/bids-specification/branch/master/graph/badge.svg)](https://codecov.io/gh/bids-standard/bids-specification)
  [![Gentoo (::science)](https://repology.org/badge/version-for-repo/gentoo_ovl_science/bidsschematools.svg?header=Gentoo%20%28%3A%3Ascience%29)](https://repology.org/project/bidsschematools/versions)
  [![PyPI version fury.io](https://badge.fury.io/py/bidsschematools.svg)](https://pypi.org/project/bidsschematools/)

  A Python library (available after installation in the Python interpreter as `bidsschematools`)
  for working with the [Brain Imaging Data Structure (BIDS)](https://bids.neuroimaging.io/) schema.

  Features:
  * lightweight
  * reference schema parsing implementation used for schema testing
  * simple CLI bindings (e.g. `bst export`)

  If you have questions, you can post them in one of several channels where BIDS members are active:
    - the [NeuroStars](https://neurostars.org/tags/bids) discourse forum
    - the [BrainHack Mattermost](https://mattermost.brainhack.org),
      for instant messaging (see also this [news item](https://bids.neuroimaging.io/2020/06/24/Join-the-BIDS-community-on-the-BrainHack-Mattermost.html))
    - the [Google group](https://groups.google.com/forum/#!forum/bids-discussion),
      for broader discussions surrounding BIDS


pkgId: f2cb4914e617ff31b7433e6df72aa9eb803c1450a7c3a3c211a942a312627f6a
name: python-bifabrik
arch: src
version: 0.10.0
epoch: 0
release: 1
summary: Microsoft Fabric ETL toolbox
url: https://rjankovic.github.io/bifabrik/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bifabrik-0.10.0-1.src.rpm
checksum_type: sha256
description:
  Microsoft Fabric ETL toolbox


   - make BI development in Microsoft Fabric easier by providing a fluent API for common ETL tasks
   - reduce repetitive code by setting preferences in config files

  See the **[project page](https://rjankovic.github.io/bifabrik/)** for info on all the features and check the **[changelog](https://github.com/rjankovic/bifabrik/blob/main/CHANGELOG.md)** to see what's new.

  If you find a problem or have a feature request, please submit it here: https://github.com/rjankovic/bifabrik/issues. Thanks!



  First, let's install the library. Either add the `bifabrik` library to an environment in Fabric and attach that environment to your notebook.

  Or you can add `%pip install bifabrik` at the beginning of the notebook.



  ```python
  import bifabrik as bif
  ```

  Also, make sure that your notebook is connected to a lakehouse. This is the lakehouse to which bifabrik will save data by default.

  ![default_lakehouse](https://github.com/rjankovic/bifabrik/assets/2221666/60951119-b0ce-40b1-8e7e-ba07b78ac06a)

  You can also configure it to [target different lakehouses](https://rjankovic.github.io/bifabrik/tutorial/cfg_storage.html).


  Simple tasks should be easy.

  ```python
  import bifabrik as bif
  bif.fromCsv('Files/CsvFiles/annual-enterprise-survey-2021.csv').toTable('Survey2021').run()
  ```
  ...and the table is in place

  ```python
  display(spark.sql('SELECT * FROM Survey2021'))
  ```
  Or you can make use of pattern matching
  ```python

  bif.fromCsv('Files/*/annual-enterprise-survey-*.csv').toTable('SurveyAll').run()
  ```
  These are full loads, overwriting the target table if it exists.


  Is your CSV is a bit...special? No problem, we'll tend to it.

  Let's say you have a European CSV with commas instead of decimal points and semicolons instead of commas as separators.
  ```python
  bif.fromCsv("Files/CsvFiles/dimBranch.csv").delimiter(';').decimal(',').toTable('DimBranch').run()
  ```

  The backend uses pandas, so you can take advantage of many other options - see `help(bif.fromCsv())`


  What, you have more files like that?  Well then, you probably don't want to repeat the setup each time.
  Good news is, the bifabrik object can keep all your preferences:

  ```python
  import bifabrik as bif


  bif.config.csv.delimiter = ';'
  bif.config.csv.decimal = ','


  bif.fromCsv("Files/CsvFiles/dimBranch.csv").toTable('DimBranch').run()
  bif.fromCsv("Files/CsvFiles/dimDepartment.csv").toTable('DimDepartment').run()
  bif.fromCsv("Files/CsvFiles/dimDivision.csv").toTable('DimDivision').run()


  ```
  If you want to persist your configuration beyond the PySpark session, you can save it to a JSON file - see [Configuration](https://rjankovic.github.io/bifabrik/tutorial/configuration.html)

  > Consistent configuration is one of the core values of the project.
  >
  > We like our lakehouses to be uniform in terms of loading patterns, table structures, tracking, etc. At the same time, we want to keep it [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
  >
  > bifabrik configuration aims to cover many aspects of the lakehouse so that you can define your conventions once, use it repeatedly, and override when neccessary.



  Enough with the files! Let's make a simple Spark SQL transformation, writing data to another SQL table - a straightforward full load:

  ```python
  bif.fromSql('''

  SELECT Industry_name_NZSIOC AS Industry_Name
  ,AVG(`Value`) AS AvgValue
  FROM LakeHouse1.Survey2021
  WHERE Variable_Code = 'H35'
  GROUP BY Industry_name_NZSIOC

  ''').toTable('SurveySummarized').run()



  ```



  `bifabrik` can help with incremental loads, identity columsn (auto-increment), dataframe transformations and more

  For example
  ```python
  import bifabrik as bif
  from pyspark.sql.functions import col, upper

  (
  bif
    .fromCsv('CsvFiles/fact_append_*.csv')
    .transformSparkDf(lambda df: df.withColumn('CodeUppercase', upper(col('Code'))))
    .toTable('SnapshotTable1')
    .increment('snapshot')
    .snapshotKeyColumns(['Date', 'Code'])
    .identityColumnPattern('{tablename}ID')
    .run()
  )
  ```
  For more details, see the **[project page](https://rjankovic.github.io/bifabrik/)**


pkgId: 88c4dae72a706b90bb5c7f9d1404dfcc8399a8d4933d14494619d70716f1e90c
name: python-bifabrik
arch: src
version: 0.10.5
epoch: 0
release: 1
summary: Microsoft Fabric ETL toolbox
url: https://rjankovic.github.io/bifabrik/
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bifabrik-0.10.5-1.src.rpm
checksum_type: sha256
description:
  Microsoft Fabric ETL toolbox


   - make BI development in Microsoft Fabric easier by providing a fluent API for common ETL tasks
   - reduce repetitive code by setting preferences in config files

  See the **[project page](https://rjankovic.github.io/bifabrik/)** for info on all the features and check the **[changelog](https://github.com/rjankovic/bifabrik/blob/main/CHANGELOG.md)** to see what's new.

  If you find a problem or have a feature request, please submit it here: https://github.com/rjankovic/bifabrik/issues. Thanks!



  First, let's install the library. Either add the `bifabrik` library to an environment in Fabric and attach that environment to your notebook.

  Or you can add `%pip install bifabrik` at the beginning of the notebook.



  ```python
  import bifabrik as bif
  ```

  Also, make sure that your notebook is connected to a lakehouse. This is the lakehouse to which bifabrik will save data by default.

  ![default_lakehouse](https://github.com/rjankovic/bifabrik/assets/2221666/60951119-b0ce-40b1-8e7e-ba07b78ac06a)

  You can also configure it to [target different lakehouses](https://rjankovic.github.io/bifabrik/tutorial/cfg_storage.html).


  Simple tasks should be easy.

  ```python
  import bifabrik as bif
  bif.fromCsv('Files/CsvFiles/annual-enterprise-survey-2021.csv').toTable('Survey2021').run()
  ```
  ...and the table is in place

  ```python
  display(spark.sql('SELECT * FROM Survey2021'))
  ```
  Or you can make use of pattern matching
  ```python

  bif.fromCsv('Files/*/annual-enterprise-survey-*.csv').toTable('SurveyAll').run()
  ```
  These are full loads, overwriting the target table if it exists.


  Is your CSV is a bit...special? No problem, we'll tend to it.

  Let's say you have a European CSV with commas instead of decimal points and semicolons instead of commas as separators.
  ```python
  bif.fromCsv("Files/CsvFiles/dimBranch.csv").delimiter(';').decimal(',').toTable('DimBranch').run()
  ```

  The backend uses pandas, so you can take advantage of many other options - see `help(bif.fromCsv())`


  What, you have more files like that?  Well then, you probably don't want to repeat the setup each time.
  Good news is, the bifabrik object can keep all your preferences:

  ```python
  import bifabrik as bif


  bif.config.csv.delimiter = ';'
  bif.config.csv.decimal = ','


  bif.fromCsv("Files/CsvFiles/dimBranch.csv").toTable('DimBranch').run()
  bif.fromCsv("Files/CsvFiles/dimDepartment.csv").toTable('DimDepartment').run()
  bif.fromCsv("Files/CsvFiles/dimDivision.csv").toTable('DimDivision').run()


  ```
  If you want to persist your configuration beyond the PySpark session, you can save it to a JSON file - see [Configuration](https://rjankovic.github.io/bifabrik/tutorial/configuration.html)

  > Consistent configuration is one of the core values of the project.
  >
  > We like our lakehouses to be uniform in terms of loading patterns, table structures, tracking, etc. At the same time, we want to keep it [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
  >
  > bifabrik configuration aims to cover many aspects of the lakehouse so that you can define your conventions once, use it repeatedly, and override when neccessary.



  Enough with the files! Let's make a simple Spark SQL transformation, writing data to another SQL table - a straightforward full load:

  ```python
  bif.fromSql('''

  SELECT Industry_name_NZSIOC AS Industry_Name
  ,AVG(`Value`) AS AvgValue
  FROM LakeHouse1.Survey2021
  WHERE Variable_Code = 'H35'
  GROUP BY Industry_name_NZSIOC

  ''').toTable('SurveySummarized').run()



  ```



  `bifabrik` can help with incremental loads, identity columns (auto-increment), dataframe transformations and more

  For example
  ```python
  import bifabrik as bif
  from pyspark.sql.functions import col, upper

  (
  bif
    .fromCsv('CsvFiles/fact_append_*.csv')
    .transformSparkDf(lambda df: df.withColumn('CodeUppercase', upper(col('Code'))))
    .toTable('SnapshotTable1')
    .increment('snapshot')
    .snapshotKeyColumns(['Date', 'Code'])
    .identityColumnPattern('{tablename}ID')
    .run()
  )
  ```
  For more details, see the **[project page](https://rjankovic.github.io/bifabrik/)**


pkgId: 545f34eff5c3010a5f273b8cf11aafb1b42bfe9a0e08f8c60d3bcd97fa43e718
name: python-bifixer
arch: src
version: 0.8.11
epoch: 0
release: 1
summary: please add a summary manually as the author left a blank one
url: https://pypi.org/project/bifixer/
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bifixer-0.8.11-1.src.rpm
checksum_type: sha256
description:
  Tool to fix bitexts and tag near-duplicates for removal.

  ![License](https://img.shields.io/badge/License-GPLv3-blue.svg)



  * Fixes several text issues:
    * Fixes mojibake
    * Turns HTML entities into the character they represent
    * Replaces characters from wrong alphabets with the correct ones
    * Deactivate this feature with `--ignore_characters`
  * Normalizes punctuation and spaces (deactivate this feature with `--ignore_normalization`)
  * Removes HTML tags (deactivate this feature with `--ignore_html`)
  * Fixes common orthographic errors for some languages:
    * Currently: Danish, German, English, Spanish, Dutch, Norwegian, Portuguese and Turkish
    * Deactivate this feature with `--ignore_orthography`
  * Removes sentences with empty source or target (deactivate this feature with `--ignore_empty`)
  * Fixes common tokenization issues (deactivate this feature with `--ignore_detokenization`)
  * Obtains hahes of parallel sentences, in order to ease the later removal of duplicates (deactivate this feature with `--ignore_duplicates`)
    * Want stronger deduplication? Make this feature to find near-duplicated sentences (ignoring casing, accents, diacritics and digits) by using the  `--aggressive_dedup` flag
    * Learn more in the "Tagging duplicated and near-duplicated sentences" section below.
  * Provides better segmentation of long sentences:
    * Choose between [NLTK](https://www.nltk.org/)  or [Loomchild](https://github.com/mbanon/segment) (SRX-rules based) segmenter modules with `--segmenter`  (default is NLTK)
    * Choose the minimum length (in words) you want to start segmenting at (default is 15) with `--words_before_segmenting`. Set it to 1 to try to segment all sentences.
    * Deactivate this feature with `--ignore_segmentation`
  * Got MONOLINGUAL text? Use `monofixer.py` instead.



  If you find Bifixer useful, please consider citing the following paper:

  > Gema Ramírez-Sánchez, Jaume Zaragoza-Bernabeu, Marta Bañón and Sergio Ortiz Rojas \
  > "[Bifixer and Bicleaner: two open-source tools to clean your parallel data.](https://eamt2020.inesc-id.pt/proceedings-eamt2020.pdf#page=311)",\
  >in *Proceedings of the 22nd Annual Conference of the European Association for Machine Translation*.\
  >Lisboa, Portugal: European Association for Machine Translation, November 2020

  ```latex
  @InProceedings{prompsit:2020:EAMT,
    author    = {Gema Ram\'{i}rez-S\'{a}nchez and Jaume Zaragoza-Bernabeu and Marta Ba{\~n}\'{o}n and Sergio Ortiz-Rojas},
    title     = {Bifixer and Bicleaner: two open-source tools to clean your parallel data.},
    booktitle = {Proceedings of the 22nd Annual Conference of the European Association for Machine Translation},
    pages	    = {291--298},
    isbn      = {978-989-33-0589-8},
    year	    = {2020},
    month     = {November},
    address   = {Lisboa, Portugal},
    publisher = {European Association for Machine Translation}
  }
  ```



  Install from source:

  ```bash
  git clone https://github.com/bitextor/bifixer
  cd bifixer
  pip install .
  ```

  Automatic testing was added to ensure that everything is working fine in Bifixer:

  ```bash
  cd bifixer
  pytest
  ```

  Or install without manually downloading the repo:

  ```bash
  pip install "bifixer @ git+https://github.com/bitextor/bifixer.git"
  ```
  Or even easier, install directly from PyPI:
  ```bash
  pip install bifixer
  ```

  Also, you can install the conda package:

  ```bash
  conda install -c bitextor bifixer
  ```

  After installing, two executables (`bifixer` and `monofixer`) will be available to be run.



  Please note that, in order to use the optional `loomchild` segmenter module in Java, it has to be specified as an optional dependency during installation:

  ```bash
  pip install bifixer[loomchild]
  ```

  In case you are not using Java 8 as default, download it and overwrite the 'JAVA_HOME' variable before installing, for example:

  ```bash
  export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/
  ```





  ```bash
  usage: bifixer.py [-h] [--header] [--scol SCOL] [--tcol TCOL]
                    [--sdeferredcol SDEFERREDCOL] [--tdeferredcol TDEFERREDCOL]
                    [--ignore_characters] [--ignore_empty] [--ignore_long]
                    [--ignore_orthography] [--ignore_detokenization]
                    [--ignore_duplicates] [--aggressive_dedup]
                    [--ignore_segmentation] [--ignore_html]
                    [--words_before_segmenting WORDS_BEFORE_SEGMENTING]
                    [--segmenter {nltk,loomchild}] [--annotated_output] [--tmp_dir TMP_DIR] [-q]
                    [--debug] [--logfile LOGFILE] [-v]
                    input output srclang trglang

  positional arguments:
    input                 Tab-separated files to be bifixed
    output                Fixed corpus
    srclang               Source language (SL) of the input
    trglang               Target language (TL) of the input

  optional arguments:
    -h, --help            show this help message and exit

  Optional:
    --header              Input file will have header (default: False)
    --scol SCOL           Source sentence column (starting in 1). The name of
                          the field is expected instead of the position if
                          --header is set (default: 3)
    --tcol TCOL           Target sentence column (starting in 1). The name of
                          the field is expected instead of the position if
                          --header is set (default: 4)
    --sdeferredcol SDEFERREDCOL
                          Source deferred standoff annotation column (starting
                          in 1). The name of the field is expected instead of
                          the position if --header is set (default: None)
    --tdeferredcol TDEFERREDCOL
                          Target deferred standoff annotation column (starting
                          in 1). The name of the field is expected instead of
                          the position if --header is set (default: None)
    --ignore_characters   Doesn't fix mojibake, orthography, or other character
                          issues (default: False)
    --ignore_empty        Doesn't remove sentences with empty source or target
                          (default: False)
    --ignore_long         Doesn't ignore too long sentences (default: False)
    --ignore_orthography  Doesn't apply orthography fixing (default: False)
    --ignore_html		Doesn't remove HTML tags (default: False)
    --ignore_detokenization
                          Doesn't fix common tokenization issues (default:
                          False)
    --ignore_duplicates   Doesn't obtain the hashes of parallel sentences
                          (default: False)
    --aggressive_dedup    Treats similar sentences as duplicates (marking them
                          with the same hash) (default: False)
    --ignore_segmentation
                          Doesn't change segmentation of long sentences
                          (default: False)
    --words_before_segmenting WORDS_BEFORE_SEGMENTING
                          Max words allowed in one side of a parallel sentence
                          before trying to segmentate it. Set to 0 to applicate
                          segmentation on everything. (default: 15)
    --segmenter {nltk,loomchild}
                          Segmenter module. (default: nltk)
    --annotated_output    Adds an extra column indicating if the sentence pair was modified
  			 ('Yes' if it was modified, otherwise 'No') (default: False)

    --tmp_dir TMP_DIR     Temporary directory where creating the temporary files
                          of this program (default: /tmp)

  Logging:
    -q, --quiet           Silent logging mode (default: False)
    --debug               Debug logging mode (default: False)
    --logfile LOGFILE     Store log to a file (default: <_io.TextIOWrapper
                          name='<stderr>' mode='w' encoding='UTF-8'>)
    -v, --version         show version of this script and exit
  ```



  * Positional:
    * INPUT : Input file. Tab-separated bilingual input file. By default, the expected columns are: SRC_URL TRG_URL SRC_SENTENCE TRG_SENTENCE [EXTRA COLUMNS]. When INPUT is -, reads standard input.
    * OUTPUT : Output file. Tab-separated bilingual output file, being a fixed version of the input file. By default, the output columns are: SRC_URL TRG_URL SRC_SENTENCE TRG_SENTENCE [EXTRA COLUMNS] HASH RANKING. When OUTPUT is -, writes standard input.
    * SRC LANG : Source language code (2-letter ISO 639-1 code)
    * TRG LANG : Target language code (2-letter ISO 639-1 code)
  * Optional:
    * --tmp_dir TMP_DIR : Directory for temporary files
    * --header : Treats the first sentence of the input file as the header row. If set, the output will contain a header as well
    * --scol SCOL : Position of the source sentence column (starting in 1). If `--header` is set, the expected value will be the name of the field. Default: 3 if `--header` is not set else src_text
    * --tcol TCOL : Position of the target sentence column (starting in 1). If `--header` is set, the expected value will be the name of the field. Default: 4 if `--header` is not set else trg_text
    * --sdeferredcol SDEFERREDCOL : Source deferred standoff annotation column (starting in 1). Default: None
    * --tdeferredcol TDEFERREDCOL : Target deferred standoff annotation column (starting in 1). Default: None
    * --ignore_duplicates : Deactivates deduplication (won't add hash or ranking)
    * --ignore_empty : Doesn't remove sentences with empty source or target
    * --ignore_long: Doesn't remove too long sentences
    * --ignore_html: Doesn't remove HTML tags
    * --ignore_segmentation : Deactivates segmentation of long sentences
    * --segmenter: Segmenter module (`nltk` or `loomchild`). Default: nltk
    * --words_before_segmenting : Maximum allowed amount of words in a sentence, before trying to segment it. Default: 15
    * --ignore_characters : Deactivates text fixing (characters, encoding...)
    * --ignore_orthography  Deactivates orthography fixing
    * --ignore_detokenization : Doesn't fix common tokenization issues.
    * --aggressive_dedup : Treats near-duplicated sentences as duplicates (normalizes sentences before hashing)
    *  --annotated_output    Adds an extra column indicating if the sentence pair was modified ('Yes' if it was modified, otherwise 'No'). Default: False
    * --tmp_dir TMP_DIR : Directory for temporary files
    * -q, --quiet : Silent logging mode
    * --debug: Shows debug messages while running
    * --logfile LOGFILE : Stores log into a file
    * -v, --version : Shows version number and exits
    * -h, --help: Shows help and exits




  ```bash
  python3.7 bifixer/monofixer.py --help
  usage: monofixer.py [-h]
                      [--scol SCOL] [--sdeferredcol SDEFERREDCOL]
                      [--ignore_characters] [--ignore_long]
                      [--ignore_orthography] [--ignore_detokenization]
                      [--ignore_duplicates] [--aggressive_dedup]
                      [--ignore_segmentation] [--ignore_html]
                      [--words_before_segmenting WORDS_BEFORE_SEGMENTING]
                      [--segmenter {nltk,loomchild}] [--annotated_output] [--tmp_dir TMP_DIR] [-q]
                      [--debug] [--logfile LOGFILE] [-v]
                      input output lang

  positional arguments:
    input                 Tab-separated file to be fixed
    output                Fixed corpus
    lang                  Language of the input

  optional arguments:
    -h, --help            show this help message and exit

  Optional:
    --header              Input file will have header (default: False)
    --scol SCOL           Sentence column (starting in 1). The name of the
                          field is expected instead of the position if --header
                          is set (default: 2)
    --sdeferredcol SDEFERREDCOL
                          Source deferred standoff annotation column (starting
                          in 1). The name of the field is expected instead of
                          the position if --header is set (default: None)
    --ignore_characters   Doesn't fix mojibake, orthography, or other character
                          issues (default: False)
    --ignore_long         Doesn't ignore too long sentences (default: False)
    --ignore_orthography  Doesn't apply orthography fixing (default: False)
    --ignore_detokenization
                          Doesn't fix common tokenization issues (default:
                          False)
    --ignore_html		Doesn't remove HTML tags (default: False)
    --ignore_duplicates   Doesn't obtain the hashes of sentences (default:
                          False)
    --aggressive_dedup    Treats similar sentences as duplicates (marking them
                          with the same hash) (default: False)
    --ignore_segmentation
                          Doesn't change segmentation of long sentences
                          (default: False)
    --words_before_segmenting WORDS_BEFORE_SEGMENTING
                          Max words allowed in a parallel sentence before trying
                          to segmentate it. Set to 0 to applicate segmentation
                          on everyt33hing. (default: 15)
    --segmenter {nltk,loomchild}
                          Segmenter module. (default: nltk)
    --annotated_output    Adds an extra column indicating if the sentence  was
  			 modified ('Yes' if it was modified, otherwise 'No')
  			 (default: False)
    --tmp_dir TMP_DIR     Temporary directory where creating the temporary files
                          of this program (default: /tmp)

  Logging:
    -q, --quiet           Silent logging mode (default: False)
    --debug               Debug logging mode (default: False)
    --logfile LOGFILE     Store log to a file (default: <_io.TextIOWrapper
                          name='<stderr>' mode='w' encoding='UTF-8'>)
    -v, --version         show version of this script and exit
  ```



  * Positional:
    * INPUT : Input file. Tab-separated monolingual input file. By default, the expected columns are: URL SENTENCE [EXTRA COLUMNS]. When INPUT is -, reads standard input.
    * OUTPUT : Output file. Tab-separated monolingual output file, being a fixed version of the input file. By default, the output columns are: URL SENTENCE [EXTRA COLUMNS] HASH RANKING. When OUTPUT is -, writes standard input.
    * LANG : Sentence language code (2-letter ISO 639-1 code)
  * Optional:
    * --tmp_dir TMP_DIR : Directory for temporary files
    * --header : Treats the first sentence of the input file as the header row. If set, the output will contain a header as well
    * --scol SCOL : Position of the source sentence column (starting in 1). If `--header` is set, the expected value will be the name of the field. Default: 2 if `--header` is not set else src_text
    * --sdeferredcol SDEFERREDCOL  Sentence deferred standoff annotation column (starting in 1). Default: None
    * --ignore_duplicates : Deactivates deduplication (won't add hash or ranking)
    * --ignore_long: Doesn't remove too long sentences
    * --ignore_html: Doesn't remove HTML tags
    * --ignore_segmentation : Deactivates segmentation of long sentences
    * --segmenter: Segmenter module (`nltk` or `loomchild`). Default: nltk
    * --words_before_segmenting : Maximum allowed amount of words in a sentence, before trying to segment it. Default: 15
    * --ignore_characters : Deactivates text fixing (characters, encoding...)
    * --ignore_orthography : Deactivates orthography fixing
    * --ignore_detokenization : Doesn't fix common tokenization issues.
    * --aggressive_dedup : Treats near-duplicated sentences as duplicates (normalizes sentences before hashing)
    * --annotated_output    Adds an extra column indicating if the sentence was modified ('Yes' if it was modified, otherwise 'No'). Default: False
    * --tmp_dir TMP_DIR : Directory for temporary files
    * -q, --quiet : Silent logging mode
    * --debug: Shows debug messages while running
    * --logfile LOGFILE : Stores log into a file
    * -v, --version : Shows version number and exits
    * -h, --help: Shows help and exits






  ```bash
  bifixer input-corpus.en-es output-corpus.en-es en es
  ```



  `bifixer` can be parallelized by using your favourite method (for example, GNU parallel)

  Suggested usage:

  ```bash
  cat input-corpus.en-es \
      | parallel -j 25 --pipe -k -l 30000 bifixer -q - - en es \
      > output-corpus.en-es
  ```

  where the two '`-`' mean read from stdin and write to stdout, and the `-q` tells bifixer to be quiet in order to avoid logging a lot of information messages.



  In order to ease the later removal of duplicated or near-duplicated parallel sentences, Bifixer appends each parallel sentence two new fields: `hash`and `ranking`.

  The hash is obtained by using the [XXHash](http://www.xxhash.com) algorithm, applied after fixing source and target sentences  (`fixed_source+"\t"+fixed_target`). Sentences that are identical at this step (see example below) will get the same hash.

  When using the `--aggressive_dedup` feature, fixed parallel sentences are also normalized (ignoring casing, accents and diacritics) before their hash is computed. Doing so, sentences that are near-duplicates (i.e. they only differ in casing or accents) will also get the same hash. Normalization is only used internally: the output sentences will not be normalized after Bifixer is applied.

  A `ranking` column is added at the end of each line. When not using the `--aggressive_dedup` feature, the number is set to 1 by default. When using the `--aggressive_dedup` feature, a float number is provided. This number (interpreted as the higher the better) will be used at later step to help the deduplication algorithm to choose the best sentence from those sharing the same hash. If the ranking number is exactly the same for a group of sentences sharing the same hash, only a random one should be kept. Otherwise, the one with the highest ranking number should be kept.



  Input file:

  ```text
  http://www.ehyz.com/2.html.tmp	http://www.ehyz.com/2.html.tmp	1 year ago NuVid	Hace 1 aÃ±o NuVid
  http://pandafoundation.com/index.php?page=7	http://pandafoundation.com/index.php?page=26	©2007 Chengdu Research Base of Giant Panda Breeding ! All Rights Reserved	©2017 Fundación para la Investigación de Cría del Panda Gigante de Chengdu/ ¡Todos los derechos reservados!
  http://www.boliviamall.com/4520.html	http://www.boliviamall.com/4520.html	Welcome Guest 1! Would you like to log in ?	Bienvenido Invitado 1! ¿Le gustaria entrar ?
  http://pandafoundation.com/index.php?page=157	http://pandafoundation.com/index.php?page=76	©2007 Chengdu Research Base of Giant Panda Breeding ! All Rights Reserved	©2017 Fundación para la Investigación de Cría del Panda Gigante de Chengdu/ ¡Todos los derechos reservados!
  http://www.ehyz.com/6.html.tmp	http://www.ehyz.com/6.html.tmp	1 year ago NuVid	Hace 1 año NuVid
  http://www.boliviamall.com/4305.html	http://www.boliviamall.com/4305.html	Welcome Guest 12! Would you like to log in?	¡Bienvenido invitado 12! ¿Le gustaria entrar?
  ```

  Output file (using the '--aggressive_dedup' feature, otherwise ranking number would be 1 in all cases):

  ```text

  http://www.ehyz.com/2.html.tmp	http://www.ehyz.com/2.html.tmp	1 year ago NuVid	Hace 1 año NuVid	9f1f7c6fc775a23a	88.25
  http://pandafoundation.com/index.php?page=7	http://pandafoundation.com/index.php?page=26	©2007 Chengdu Research Base of Giant Panda Breeding ! All Rights Reserved	©2017 Fundación para la Investigación de Cría del Panda Gigante de Chengdu/ ¡Todos los derechos reservados!	d0278d1279f06823	91.93
  http://www.boliviamall.com/4520.html	http://www.boliviamall.com/4520.html	Welcome Guest 1! Would you like to log in ?	Bienvenido Invitado 1! ¿Le gustaría entrar ?	e8f129b1624b9f5d	91.22
  http://pandafoundation.com/index.php?page=157	http://pandafoundation.com/index.php?page=76	©2007 Chengdu Research Base of Giant Panda Breeding ! All Rights Reserved	©2017 Fundación para la Investigación de Cría del Panda Gigante de Chengdu/ ¡Todos los derechos reservados!	d0278d1279f06823	91.93
  http://www.ehyz.com/6.html.tmp	http://www.ehyz.com/6.html.tmp	1 year ago NuVid	Hace 1 año NuVid	9f1f7c6fc775a23a	88.25
  http://www.boliviamall.com/4305.html	http://www.boliviamall.com/4305.html	Welcome Guest 12! Would you like to log in?	¡Bienvenido invitado 12! ¿Le gustaría entrar?	422aeefd8f056b30	92.78
  ```

  ___

  ![Connecting Europe Facility](https://www.paracrawl.eu/images/logo_en_cef273x39.png)

  All documents and software contained in this repository reflect only the authors' view. The Innovation and Networks Executive Agency of the European Union is not responsible for any use that may be made of the information it contains.

rpm_license:
[cell repeats an earlier one in this file: 34486 characters]


pkgId: 3f77c0a4128b8f938950edc744173e9b1abd10f9abf24aad63e9676ff9b99ff1
name: python-bifrost-bridge
arch: src
version: 0.0.1
epoch: 0
release: 1
summary: TODO
url: https://github.com/$GIT_USER_NAME/bifrost_bridge
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bifrost-bridge-0.0.1-1.src.rpm
checksum_type: sha256
description:
  <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

  This file will become your README and also the index of your
  documentation.



  If you are new to using `nbdev` here are some useful pointers to get you
  started.



  ``` sh

  $ pip install -e .





  $ nbdev_prepare
  ```





  Install latest from the GitHub
  [repository](https://github.com/$GIT_USER_NAME/bifrost_bridge):

  ``` sh
  $ pip install git+https://github.com/$GIT_USER_NAME/bifrost_bridge.git
  ```

  or from [conda](https://anaconda.org/$GIT_USER_NAME/bifrost_bridge)

  ``` sh
  $ conda install -c $GIT_USER_NAME bifrost_bridge
  ```

  or from [pypi](https://pypi.org/project/bifrost_bridge/)

  ``` sh
  $ pip install bifrost_bridge
  ```



  Documentation can be found hosted on this GitHub
  [repository](https://github.com/$GIT_USER_NAME/bifrost_bridge)’s
  [pages](https://$GIT_USER_NAME.github.io/bifrost_bridge/). Additionally
  you can find package manager specific guidelines on
  [conda](https://anaconda.org/$GIT_USER_NAME/bifrost_bridge) and
  [pypi](https://pypi.org/project/bifrost_bridge/) respectively.



  Fill me in please! Don’t forget code examples:

  ``` python
  1+1
  ```

      2


pkgId: d679730d4104ee285a9aa82cf7c172a81ea6d26e74cd9b4b853cff8f7ee9e493
name: python-big
arch: src
version: 0.12.3
epoch: 0
release: 1
summary: The big package is a grab-bag of cool code for use in your programs.
url: https://pypi.org/project/big/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-big-0.12.3-1.src.rpm
checksum_type: sha256
description:
  <ol><li id="re_strip_footnote"><p>
  And I should know--`multisplit` is implemented using `re.split`!
  </p></li></ol>
  </dd></dl>

  <dl><dd>

  Several functions in **big** take a `separators`
  argument, an iterable of separator strings.
  Examples of these functions include
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
  and
  [`multisplit`.](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
  Although you can use any iterable of strings
  you like, most often you'll be separating on some
  form of whitespace.  But what, exactly, *is*
  whitespace?  There's more to this topic than you
  might suspect.
  The good news is, you can almost certainly ignore all the
  complexity.  These days the only whitespace characters you're
  likely to encounter are spaces, tabs, newlines, and maybe
  carriage returns.  Python and **big** handle all those easily.
  With respect to **big** and these `separators` arguments,
  **big** provides four values designed for use as `separators`.
  All four of these are tuples containing whitespace characters:
  * When working with `str` objects, you'll want to use either
    [`big.whitespace`](#whitespace) or [`big.linebreaks`.](#linebreaks)
    `big.whitespace` contains all the whitespace characters,
    `big.linebreaks` contains just the line-breaking
    whitespace characters.
  * **big** also has equivalents for working with `bytes`
    objects: [`bytes_whitespace`](#bytes_whitespace)
    and [`bytes_linebreaks`,](#bytes_linebreaks)
    respectively.
  Apart from exceptionally rare occasions, these are all you'll ever need.
  And if that's all you need, you can stop reading this
  section now.
  But what about those exceptionally rare occasions?
  You'll be pleased to know **big** handles them too.
  The rest of this section is
  a deep dive into these rare occasions.

  Here's the list of all characters recognized by
  Python `str` objects as whitespace characters:
      
      
      '\t'    , #     9 - 0x0009 - tab
      '\n'    , #    10 - 0x000a - newline
      '\v'    , #    11 - 0x000b - vertical tab
      '\f'    , #    12 - 0x000c - form feed
      '\r'    , #    13 - 0x000d - carriage return
      '\x1c'  , #    28 - 0x001c - file separator
      '\x1d'  , #    29 - 0x001d - group separator
      '\x1e'  , #    30 - 0x001e - record separator
      '\x1f'  , #    31 - 0x001f - unit separator
      ' '     , #    32 - 0x0020 - space
      '\x85'  , #   133 - 0x0085 - next line
      '\xa0'  , #   160 - 0x00a0 - non-breaking space
      '\u1680', #  5760 - 0x1680 - ogham space mark
      '\u2000', #  8192 - 0x2000 - en quad
      '\u2001', #  8193 - 0x2001 - em quad
      '\u2002', #  8194 - 0x2002 - en space
      '\u2003', #  8195 - 0x2003 - em space
      '\u2004', #  8196 - 0x2004 - three-per-em space
      '\u2005', #  8197 - 0x2005 - four-per-em space
      '\u2006', #  8198 - 0x2006 - six-per-em space
      '\u2007', #  8199 - 0x2007 - figure space
      '\u2008', #  8200 - 0x2008 - punctuation space
      '\u2009', #  8201 - 0x2009 - thin space
      '\u200a', #  8202 - 0x200a - hair space
      '\u2028', #  8232 - 0x2028 - line separator
      '\u2029', #  8233 - 0x2029 - paragraph separator
      '\u202f', #  8239 - 0x202f - narrow no-break space
      '\u205f', #  8287 - 0x205f - medium mathematical space
      '\u3000', # 12288 - 0x3000 - ideographic space
  This list was derived by iterating over every character
  defined in Unicode, and testing to see if the `split()`
  method on a Python `str` object splits at that character.
  The first surprise: this *isn't* the same as the list of
  [all characters defined by Unicode as whitespace.](https://en.wikipedia.org/wiki/Whitespace_character#Unicode)
  It's *almost* the same list, except Python adds four extra
  characters: `'\x1c'`,  `'\x1d'`,  `'\x1e'`, and `'\x1f'`,
  which respectively are called "file separator", "group separator",
  "record separator", and "unit separator".
  I'll refer to these as "the four ASCII separator characters".
  These characters were defined as part of [the original ASCII
  standard,](https://en.wikipedia.org/wiki/ASCII) way back in 1963.
  As their names suggest, they were intended to be used as separator
  characters for data, the same way
  [Ctrl-Z was used to indicate end-of-file in the CPM and earliest
  FAT filesystems.](https://en.wikipedia.org/wiki/End-of-file#EOF_character)
  But the four ASCII separator characters were rarely used
  even back in the day.  Today they're practically unheard of.
  As a rule, printing these characters to the screen generally
  doesn't do anything--they don't move the cursor, and the
  screen doesn't change.
  So their behavior is a bit mysterious.  A lot of people (including
  early Python programmers it seems!) thought that meant they're
  whitespace.  This seems like an odd conclusion to me.  After all,
  all the other whitespace characters move the cursor, either right
  or down or both; these don't move the cursor at all.
  [The Unicode standard](https://www.unicode.org/Public/14.0.0/)
  is unambiguous: these characters are *not whitespace.*  And yet Python's
  "Unicode object" behaves as if they are.  So I'd say this is a bug;
  Python's Unicode object should implement what the Unicode standard says.
  > It seems that the C library used by GCC and clang on my workstation
  > agree.  I wrote a quick C program to print out what characters
  > are and aren't whitespace, according to the C function isspace().
  > It seems the C library agrees with Unicode: it doesn't consider
  > the four ASCII separator characters to be whitespace.
  >
  > Here's the program, in case you want to try it yourself.
  >
  >      #include <stdio.h>
  >      #include <ctype.h>
  >
  >      int main(int c, char *a[]) {
  >              int i;
  >              printf("\nisspace table.\nAdd the row and column numbers together (in hex).\n\n");
  >              printf("     | 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
  >              printf("-----+--------------------------------\n");
  >              for (i = 0 ; i < 256 ; i++) {
  >                      char *message = isspace(i) ? "Y" : "n";
  >                      if ((i % 16) == 0)
  >                              printf("0x%02x |", i);
  >                      printf(" %s", message);
  >                      if ((i % 16) == 15)
  >                              printf("\n");
  >              }
  >              return 0;
  >      }
  >
  > Here's its output on my workstation:
  >
  >     isspace table.
  >     Add the row and column numbers together (in hex).
  >
  >          | 0 1 2 3 4 5 6 7 8 9 a b c d e f
  >     -----+--------------------------------
  >     0x00 | n n n n n n n n n Y Y Y Y Y n n
  >     0x10 | n n n n n n n n n n n n n n n n
  >     0x20 | Y n n n n n n n n n n n n n n n
  >     0x30 | n n n n n n n n n n n n n n n n
  >     0x40 | n n n n n n n n n n n n n n n n
  >     0x50 | n n n n n n n n n n n n n n n n
  >     0x60 | n n n n n n n n n n n n n n n n
  >     0x70 | n n n n n n n n n n n n n n n n
  >     0x80 | n n n n n n n n n n n n n n n n
  >     0x90 | n n n n n n n n n n n n n n n n
  >     0xa0 | n n n n n n n n n n n n n n n n
  >     0xb0 | n n n n n n n n n n n n n n n n
  >     0xc0 | n n n n n n n n n n n n n n n n
  >     0xd0 | n n n n n n n n n n n n n n n n
  >     0xe0 | n n n n n n n n n n n n n n n n
  >     0xf0 | n n n n n n n n n n n n n n n n
  >
  > 0x1c through 0x1f are represented by the last four `n` characters on
  > the second line, the `0x10` line.  The fact that they're `n`s tells you
  > that this C standard library doesn't consider those characters to be
  > whitespace.
  Like many bugs, this one has lingered for a long time.  The behavior
  is present in Python 2, there's
  [a ten-year-old issue on the Python issue tracker about this,](https://github.com/python/cpython/issues/62436)
   and it's not making progress.
  The second surprise has to do with `bytes` objects.
  Of course, `bytes` objects represent binary data, and don't
  necessarily represent characters.  Even if they do, they don't
  have any encoding associated with them.  However, for
  convenience--and backwards-compatibility with Python 2--Python's
  `bytes` objects support several method calls that treat the data
  as if it were "ASCII-compatible".
  The surprise: These methods on Python `bytes` objects recognize
  a *different* set of whitespace characters.  Here's the list of
  all bytes recognized by Python `bytes` objects as whitespace:
      
      
      '\t'    , #  9 - 0x09 - tab
      '\n'    , # 10 - 0x0a - newline
      '\v'    , # 11 - 0x0b - vertical tab
      '\f'    , # 12 - 0x0c - form feed
      '\r'    , # 13 - 0x0d - carriage return
      ' '     , # 32 - 0x20 - space
  This list was derived by iterating over every possible
  byte value, and testing to see if the `split()` method
  on a Python `bytes` object splits at that byte.
  The good news is, this list is the same as ASCII's list,
  and it agrees with Unicode.
  In fact this list is quite familiar to C programmers;
  it's the same whitespace characters recognized by the
  standard C function
  [`isspace()` (in `ctypes.h`).](https://www.oreilly.com/library/view/c-in-a/0596006977/re129.html)
  Python has used this function to decide which characters
  are and aren't whitespace in 8-bit strings since its very
  beginning.
  Notice that this list *doesn't* contain the
  four ASCII separator characters.  That these two
  types in Python don't agree only enhances the mystery.

  The situation is slightly worse with line-breaking
  characters. Line-breaking characters are a subset of
  whitespace characters; they're whitespace characters that
  always move the cursor down.  And, as with whitespace
  generally, Python `str` objects don't agree with Unicode
  about what is and is not a line-breaking character,
  and Python `bytes` objects don't agree with either of those.
  Here's the list of all Unicode characters recognized by
  Python `str` objects as line-breaking characters:
      
      
      '\n'    , #   10 0x000a - newline
      '\v'    , #   11 0x000b - vertical tab
      '\f'    , #   12 0x000c - form feed
      '\r'    , #   13 0x000d - carriage return
      '\x1c'  , #   28 0x001c - file separator
      '\x1d'  , #   29 0x001d - group separator
      '\x1e'  , #   30 0x001e - record separator
      '\x85'  , #  133 0x0085 - next line
      '\u2028', # 8232 0x2028 - line separator
      '\u2029', # 8233 0x2029 - paragraph separator
  This list was derived by iterating over every character
  defined in Unicode, and testing to see if the `splitlines()`
  method on a Python `str` object splits at that character.
  Again, this is different from [the list of characters
  defined as line-breaking whitespace in Unicode.](https://en.wikipedia.org/wiki/Newline#Unicode)
  And again it's because Python defines some of the four ASCII separator
  characters as line-breaking characters.  In this case
  it's only the first three; Python doesn't consider
  the fourth, "unit separator", as a line-breaking character.
  (I don't know why Python draws this distinction...
  but then again, I don't know why it considers the
  first three to be line-breaking.  It's *all* a mystery to me.)
  Here's the list of all characters recognized by
  Python `bytes` objects as line-breaking characters:
      
      
      '\n'    , #  10 0x000a - newline
      '\r'    , #  13 0x000d - carriage return
  This list was derived by iterating over every possible
  byte, and testing to see if the `splitlines()`
  method on a Python `bytes` object splits at that byte.
  It's here we find our final unpleasant surprise:
  the methods on Python `bytes` objects *don't* consider
  `'\v'` (vertical tab)
  and
  `'\f'` (form feed)
  to be line-break characters.  I assert this is also a bug.
  These are well understood to be line-breaking characters;
  "vertical tab" is like a "tab", except it moves the cursor
  down instead of to the right.  And "form feed" moves the
  cursor to the top left of the next "page", which requires
  advancing at least one line.

  To be crystal clear: the odds that any of this will cause
  a problem for you are *extremely* low.  In order for it
  to make a difference:
  * you'd have to encounter text using one of these six characters
    where Python disagrees with Unicode and ASCII, and
  * you'd have to process the input based on some definition
    of whitespace, and
  * it would have to produce different results than you might
    have other wise expected, *and*
  * this difference in results would have to be important.
  It seems extremely unlikely that all of these will be true for you.
  In case this *does* affect you, **big** has
  a complete set of predefined whitespace tuples that will
  handle any of these situations.
  **big** defines a total of *ten* tuples, sorted into
  five categories.
  In every category there are two values: one that contains
  `whitespace`, the other contains `linebreaks`.  The
  `whitespace` tuple contains all the possible values of
  whitespace--characters that move the cursor either
  horizontally, or vertically, or both, but don't
  print anything visible to the screen.  The `linebreaks`
  tuple contains the subset of whitespace characters that
  move the cursor vertically.
  The most important two values start with `str_`:
  [`str_whitespace`](#str_whitespace)
  and
  [`str_linebreaks`.](#str_linebreaks)
  These contain all the whitespace characters
  recognized by the Python `str` object.
  Next are two values that start with `unicode_`:
  [`unicode_whitespace`](#unicode_whitespace)
  and
  [`unicode_linebreaks`.](#unicode_linebreaks)
  These contain all the whitespace characters
  defined in the Unicode standard.  They're the
  same as the `str_` tuples except we remove the
  four ASCII separator characters.
  Third, two values that start with `ascii_`:
  [`ascii_whitespace`](#ascii_whitespace)
  and
  [`ascii_linebreaks`.](#ascii_linebreaks)
  These contain all the whitespace characters
  defined in ASCII.  (Note that these contain
  `str` objects, not `bytes` objects.)  They're
  the same as the `unicode_` tuples, except we
  throw away all characters with a code point
  higher than 127.
  Fourth, two values that start with `bytes_`:
  [`bytes_whitespace`](#bytes_whitespace)
  and
  [`bytes_linebreaks`.](#bytes_linebreaks)
  These contain all the whitespace characters
  recognized by the Python `bytes` object.
  These tuples contain `bytes` objects, encoded
  using the `ascii` encoding.  The list of
  characters is distinct from the other sets
  of tuples, and was derived as described above.
  Finally we have the two tuples that lack a prefix:
  [`whitespace`](#whitespace)
  and
  [`linebreaks`.](#linebreaks)
  These are the tuples you should use most of the time,
  and several **big** functions use them as default values.
  These are simply copies of `str_whitespace` and
  `str_linebreaks` respectively.
  (**big** actually defines an *additional* ten tuples,
  as discussed in the very next section.)

  Historically, different platforms used different
  ASCII characters--or sequences of ASCII characters--to
  represent "go to the next line" in text files.  Here are the
  most popular conventions:
      \n    - UNIX, Amiga, macOS 10+
      \r    - macOS 9 and earlier, many 8-bit computers
      \r\n  - Windows, DOS
  (There are a couple more conventions, and a lot more history,
  in the [Wikipedia article on newlines.)](https://en.wikipedia.org/wiki/Newline#Representation)
  Handling these differing conventions was a stumbling block
  for a long time--both for computer programs, and in the
  daily lives of computer users.  Python went through several
  iterations on how to handle this, eventually settling on
  the ["universal newlines"](https://peps.python.org/pep-0278/)
  support added in Python 2.3.
  These days the world seems to be converging on the UNIX
  standard `'\n'`; Windows supports it, and it's the default
  on every other modern platform.
  So in practice you probably don't have end-of-line conversion
  problems, either.
  But just in case, **big** has one more trick.  All of
  the tuples defined in the previous section--from `whitespace`
  to `ascii_linebreaks`--also contain this string:
      '\r\n'
  (The two `bytes_` tuples contain the `bytes` equivalent,
  `b'\r\n`.)
  This addition means that, when you use one of these tuples
  with one of the **big** functions that take separators,
  it'll split on `\r\n` as if it was one character.  This
  means that **big** itself should automatically handle
  the DOS and Windows end-of-line character sequence, in
  case one happens to creep into your data.
  If you don't want this behavior, just add the suffix
  `_without_crlf` to the end of any of the ten tuples,
  e.g. `whitespace_without_crlf`, `bytes_linebreaks_without_crlf`.

  What if you need to split text by whitespace, or by lines,
  but that text is in `bytes` format with an unusual encoding?
  **big** makes that easy too.  If one of the builtin tuples
  won't work for you, you can can make your own tuple from scratch,
  or modify an existing tuple to meet your needs.
  For example, let's say you need to split a document by
  whitespace, and the document is encoded in [code page 850,
  aka "latin-1".](https://en.wikipedia.org/wiki/Code_page_850)
  Normally the easiest thing would be to decode it a `str` object
  using the `'latin-1'` text codec, then operate on it normally.
  But you might have reasons why you don't want to decode it--maybe
  the document is damaged and doesn't decode properly, and it's
  easier to work with the encoded bytes than to fix it.  If you
  want to process the text with a **big** function that accepts a
  `separator` argument, you could make your own custom tuple
  of "latin-1" whitespace characters.  "latin-1" has the same
  whitespace characters as ASCII, but adds one more, value 255,
  which is not line-breaking.  So it's easy to make the appropriate
  tuples yourself:
      latin_1_whitespace = big.bytes_whitespace + (b'\xff',)
      latin_1_linebreaks = big.bytes_linebreaks
  What if you want to process a `bytes` object containing
  UTF-8?  That's easy too.  Just convert one of the existing
  tuples containing `str` objects using
  [`big.encode_strings`.](#encode_stringso--encodingascii)
  For example, to split a UTF-8 encoded bytes object `b` using
  the Unicode line-breaking characters, you could call:
      multisplit(b, encode_strings(unicode_linebreaks, encoding='utf-8'))
  Note that this technique probably won't work correctly for most other
  multibyte encodings, for example [UTF-16.](https://en.wikipedia.org/wiki/UTF-16)
  For these encodings, you should decode to `str` before processing.
  Why?  It's because `multisplit` could find matches in multibyte
  sequences *straddling* characters.  Consider this example:
  ```Python
  >>> haystack = '\u0101\u0102'
  >>> needle = '\u0201'
  >>> needle in haystack
  False
  >>>
  >>> encoded_haystack = haystack.encode('utf-16-le')
  >>> encoded_needle = needle.encode('utf-16-le')
  >>> encoded_needle in encoded_haystack
  True
  ```
  The character `'\u0201'` doesn't appear in the original string,
  but the *encoded* version appears in the *encoded* string,
  as the *second* byte of the *first* character and the *first*
  byte of the *second* character:
  ```Python
  >>> encoded_haystack
  b'\x01\x01\x02\x01'
  >>> encoded_needle
  b'\x01\x02'
  ```
  </dd></dl>

  <dl><dd>
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
  is a function that makes it easy to write well-behaved, feature-rich text parsers.
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) itself
  iterates over a string, returning an iterator that yields individual lines
  split from that string.  The iterator yields a 2-tuple:
  ```
  (LinesInfo, line)
  ```
  The `LinesInfo` object provides the line number and starting column number
  for each line.  This makes it easy for your parser to provide
  line and column information for error messages.
  This iterator is designed to be modified by "lines modifier"
  functions.  These are functions that consume a lines
  iterator and re-yield the values, possibly modifying or
  discarding them along the way.  For example, passing
  a [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) iterator into `lines_filter_empty_lines` results
  in an iterator that skips over the empty lines.
  All the lines modifier functions that ship with **big**
  start with the string `lines_`.
  Most lines modifier function names belong to a category,
  encoded as the second word in the function name
  (immediately after `lines_`).  Some examples:
  * `lines_filter_` functions conditionally remove
    lines from the output.  For example, `lines_filter_empty_lines`
    will only yield a line if it isn't empty.
  * `lines_strip_` functions may remove one or
    more substrings from the line.  For example,
    [`lines_strip_indent`](#lines_strip_indentli)
    strips the leading whitespace from a line before yielding
    it.  (Whenever a lines modifier removes leading text from a line,
    it will add a `leading` field to the accompanying
    [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
    object containing the removed substring, and will also update the
    `column_number` of the line to reflect the new starting column.)
  * `lines_convert_` functions means this lines modifier may change one
    or more substrings in the line.  For example,
    `lines_convert_tabs_to_spaces` changes tab characters
    to space characters in any lines it processes.
  (**big** isn't strictly consistent about these category names though.
  For example,
  [`lines_containing`](#lines_containingli-s--invertfalse)
  and
  [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0)
  are obviously "filter" modifiers, but their names
  don't start with `lines_filter_`.)
  All lines modifier functions are composable with each
  other; you can "stack" them together simply by passing
  the output of one into the input of another.  For example,
  ```Python
       with open("textfile.txt", "rt") as f:
           for info, line in big.lines_filter_empty_lines(
       	     big.lines_rstrip(lines(f.read()))):
  ```
  will iterate over the lines of `textfile.txt`, skipping
  over all empty lines and lines that consist only of
  whitespace.
  When you stack line modifiers in this way, note that the
  *outer* modifiers happen *later*.  In the above example,
  each line is first "r-stripped", and then discarded
  if it's empty.  If you stacked the line modifiers in
  the opposite order:
  ```Python
       with open("textfile.txt", "rt") as f:
           for info, line in big.lines_rstrip(
       	     big.lines_filter_empty_lines(lines(f.read()))):
  ```
  then it'd filter out empty lines first, and *then*
  "r-strip" the lines.  So lines in the input that contained
  only whitespace would still get yielded as empty lines,
  which is probably not what you want.  Ordering is important!
  It's probably clearer to constructed nested lines modifiers
  this way:
  ```Python
       with open("textfile.txt", "rt") as f:
           li = lines(f.read())
           li = big.lines_filter_empty_lines(li)
           li = big.lines_rstrip(li)
           for info, line in li:
  ```
  This is much easier to read, particularly when one or
  more lines modifiers take additional arguments.
  Of course, you can write your own lines modifier functions.
  Simply accept a lines iterator as an argument, iterate over
  it, and yield each line info and line--modifying them
  (or not yielding them!) as you see fit.  You could
  even write your own lines iterator, a replacement for
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs),
  if you need functionality
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) doesn't provide.
  Note that if you write your own lines modifier function,
  and it removes text from the beginning the line, you must
  update `column_number` in the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  object manually--it
  doesn't happen automatically.  The easiest way to handle this
  is also the best way: whenever clipping text from the beginning
  or end of the line, use the
  [`clip_leading`](#lineinfoclip_leadingline-s)
  and
  [`clip_trailing`](#lineinfoclip_trailingline-s)
  methods on the `LineInfo` object.
  Speaking of best practices for lines modifier functions,
  it's also best practice to *modify* the *existing*
  `LineInfo` object that was yielded to you, rather than
  throwing it away, creating a new one, and yielding that
  instead.  Previous lines modifier iterators may have added
  fields to the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  that you'd to preserve.

  Generally speaking,
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects obey an invariant.
  For any `(info, line)` pair yielded by `lines` or a lines
  modifier:
      info.leading + line + info.trailing + info.end == info.line
  That is, you can recreate the original line by concatenating
  the "leading" string, the modified line, the "trailing" string,
  and the "end" string.
  Of course, this won't be true if you use lines modifiers that
  replace characters in the line.  For example, `lines_convert_tabs_to_spaces`
  replaces tab characters with one or more space characters.
  If the original line contains tabs, obviously the above invariant
  will no longer hold true.
  </dd></dl>

  <dl><dd>
  **big** contains three functions used to reflow and format text
  in a pleasing manner.  In the order you should use them, they are
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue),
  [`wrap_words(),`](#wrap_wordswords-margin79--two_spacestrue),
  and optionally
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0).
  This trio of functions gives you the following word-wrap superpowers:
  * Paragraphs of text representing embedded "code" don't get word-wrapped.
    Instead, their formatting is preserved.
  * Multiple texts can be merged together into multiple columns.

  The **big** word wrapping functions also distinguish between
  "text" and "code".  The main distinction is, "text" lines can
  get word-wrapped, but "code" lines shouldn't.  **big** considers
  any line starting with enough whitespace to be a "code" line;
  by default, this is four spaces.  Any non-blank line that
  starting with four spaces is a "code" line, and any non-blank
  line that starts with less than four spaces is a "text" line.
  In "text" mode:
  * words are separated by whitespace,
  * initial whitespace on the line is discarded,
  * the amount of whitespace between words is irrelevant,
  * individual newline characters are ignored, and
  * more than two newline characters are converted into exactly
    two newlines (aka a "paragraph break").
  In "code" mode:
  * all whitespace is preserved, except for trailing whitespace on a line, and
  * all newline characters are preserved.
  Also, whenever
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  switches between
  "text" and "code" mode, it emits a paragraph break.

  A *split text array* is an intermediary data structure
  used by **big.text** functions to represent text.
  It's literally just an array of strings, where the strings
  represent individual word-wrappable substrings.
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  returns a *split text array,* and
  [`wrap_words()`](#wrap_wordswords-margin79--two_spacestrue)
  consumes a *split text array.*
  You'll see four kinds of strings in a *split text array:*
  * Individual words, ready to be word-wrapped.
  * Entire lines of "code", preserving their formatting.
  * Line breaks, represented by a single newline: `'\n'`.
  * Paragraph breaks, represented by two newlines: `'\n\n'`.

  This might be clearer with an example or two.  The following text:
  ```
  hello there!
  this is text.
  this is a second paragraph!
  ```
  would be represented in a Python string as:
  ```Python
  "hello there!\nthis is text.\n\n\nthis is a second paragraph!"
  ```
  Note the three newlines between the second and third lines.
  If you then passed this string in to
  [`split_text_with_code`,](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  it'd return this *split text array:*
  ```Python
  [ 'hello', 'there!', 'this', 'is', 'text.', '\n\n',
    'this', 'is', 'a', 'second', 'paragraph!']
  ```
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  merged the first two lines together into
  a single paragraph, and collapsed the three newlines separating
  the two paragraphs into a "paragraph break" marker
  (two newlines in one string).
  Now let's add an example of text with some "code".  This text:
  ```
  What are the first four squared numbers?
      for i in range(1, 5):
          print(i**2)
  Python is just that easy!
  ```
  would be represented in a Python string as (broken up into multiple strings for clarity):
  ```Python
  "What are the first four squared numbers?\n\n"
  +
  "    for i in range(1, 5):\n\n\n"
  +
  "        print(i**2)\n\nPython is just that easy!"
  ```
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  considers the two lines with initial whitespace as "code" lines,
  and so the text is split into the following *split text array:*
  ```Python
  ['What', 'are', 'the', 'first', 'four', 'squared', 'numbers?', '\n\n',
    '    for i in range(1, 5):', '\n', '\n', '\n', '        print(i**2)', '\n\n',
    'Python', 'is', 'just', 'that', 'easy!']
  ```
  Here we have a "text" paragraph, followed by a "code" paragraph, followed
  by a second "text" paragraph.  The "code" paragraph preserves the internal
  newlines, though they are represented as individual "line break" markers
  (strings containing a single newline).  Every paragraph is separated by
  a "paragraph marker".
  Here's a simple algorithm for joining a *split text array* back into a
  single string:
  ```Python
  prev = None
  a = []
  for word in split_text_array:
      if not (prev and prev.isspace() and word.isspace()):
          a.append(' ')
      a.append(word)
  text = "".join(a)
  ```
  Of course, this algorithm is too simple to do word wrapping.
  Nor does it handle adding two spaces after sentence-ending
  punctuation.  In practice, you shouldn't do this by hand;
  you should use
  [`wrap_words`](#wrap_wordswords-margin79--two_spacestrue).

  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  merges multiple strings into columns on the same line.
  For example, it could merge these three Python strings:
  ```Python
  [
  "Here's the first\ncolumn of text.",
  "More text over here!\nIt's the second\ncolumn!  How\nexciting!",
  "And here's a\nthird column.",
  ]
  ```
  into the following text:
  ```
  Here's the first    More text over here!   And here's a
  column of text.     It's the second        third column.
                      column!  How
                      exciting!
  ```
  (Note that
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  doesn't do its own word-wrapping;
  instead, it's designed to consume the output of
  [`wrap_words`](#wrap_wordswords-margin79--two_spacestrue).)
  Each column is passed in to
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  as a "column tuple":
  ```Python
  (s, min_width, max_width)
  ```
  `s` is the string,
  `min_width` is the minimum width of the column, and
  `max_width` is the minimum width of the column.
  As you saw above, `s` can contain newline characters,
  and
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  obeys those when formatting each column.
  For each column,
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  measures the longest
  line of each column.  The width of the column is determined
  as follows:
  - If the longest line is less than `min_width` characters long,
    the column will be `min_width` characters wide.
  - If the longest line is less than or equal to `min_width`
    characters long, and less than or equal to `max_width`
    characters long, the column will be as wide as the longest line.
  - If the longest line is greater than `max_width` characters long,
    the column will be `max_width` characters wide, and lines that
    are longer than `max_width` characters will "overflow".

  What is "overflow"?  It's a condition
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  may encounter when the text in a column is wider than that
  column's `max_width`.
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  needs to consider both "overflow lines",
  lines that are longer than `max_width`, and "overflow columns",
  columns that contain one or more overflow lines.
  What does
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  do when it encounters overflow?
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  supports three "strategies" to deal with this condition, and you can specify
  which one you want using its `overflow_strategy` parameter.  The three
  strategies are:
  - `OverflowStrategy.RAISE`: Raise an `OverflowError` exception.  The default.
  - `OverflowStrategy.INTRUDE_ALL`: Intrude into all subsequent columns on
  all lines where the overflowed column is wider than its `max_width`.
  The subsequent columns "make space" for the overflow text by not adding
  text on those overflowed lines; this is called "pausing" their output.
  - `OverflowStrategy.DELAY_ALL`:  Delay all columns after the overflowed
  column, not beginning any until after the last overflowed line
  in the overflowed column.  This is like the `INTRUDE_ALL` strategy,
  except that the columns "make space" by pausing their output until
  the last overflowed line.
  When `overflow_strategy` is `INTRUDE_ALL` or `DELAY_ALL`, and
  either `overflow_before` or `overflow_after` is nonzero, these
  specify the number of extra lines before or after
  the overflowed lines in a column where the subsequent columns
  "pause".
  </dd></dl>

  <dl><dd>

  **big**'s [`TopologicalSorter`](#topologicalsortergraphnone)
  is a drop-in replacement for
  [`graphlib.TopologicalSorter`](https://docs.python.org/3/library/graphlib.html#graphlib.TopologicalSorter)
  in the Python standard library (new in 3.9).
  However, the version in **big** has been greatly upgraded:
  - `prepare` is now optional, though it still performs a cycle check.
  - You can add nodes and edges to a graph at any time, even while
    iterating over the graph.  Adding nodes and edges always succeeds.
  - You can remove nodes from graph `g` with the new method `g.remove(node)`.
    Again, you can do this at any time, even while iterating over the graph.
    Removing a node from the graph always succeeds, assuming the node is in the graph.
  - The functionality for iterating over a graph now lives in its own object called
    a *view*.  View objects implement the `get_ready`, `done`, and `__bool__`
    methods.  There's a default view built in to the graph object;
    the `get_ready`, `done`, and `__bool__` methods on a graph just call
    into the graph's default view.  You can create a new view at any time
    by calling the new `view` method.
  Note that if you're using a view to iterate over the graph, and you modify the graph,
  and the view now represents a state that isn't *coherent* with the graph,
  attempting to use that view raises a `RuntimeError`.  (I'll define what I mean
  by view "coherence" in the next subsection.)
  This implementation also fixes some minor warts with the existing API:
  - In Python's implementation, `static_order` and `get_ready`/`done` are mutually exclusive.  If you ever call
    `get_ready` on a graph,  you can never call `static_order`, and vice-versa.  The implementaiton in **big**
    doesn't have this restriction, because its implementation of `static_order` creates and uses a new view object
    every time it's called.
  - In Python's implementation, you can only iterate over the graph once, or call `static_order` once.
    The implementation in **big** solves this in several ways: it allows you to create as many views as you
    want, and you can call the new `reset` method on a view to reset it to its initial state.

  So what does it mean for a view to no longer be coherent with the graph?
  Consider the following code:
  ```Python
  g = big.TopologicalSorter()
  g.add('B', 'A')
  g.add('C', 'A')
  g.add('D', 'B', 'C')
  g.add('B', 'A')
  v = g.view()
  g.ready() # returns ('A',)
  g.add('A', 'Q')
  ```
  First this creates a graph `g` with a classic "diamond"
  dependency pattern.  Then it creates a new view `v`, and gets
  the currently "ready" nodes, which consists just of the node
  `'A'`.  Finally it adds a new dependency: `'A'` depends on `'Q'`.
  At this moment, view `v` is no longer _coherent._  `'A'` has been
  marked as "ready", but `'Q'` has not.  And yet `'A'` depends on `'Q'`.
  All those statements can't be true at the same time!
  So view `v` is no longer _coherent,_  and any attempt to interact
  with `v` raises an exception.
  To state it more precisely: if view `v` is a view on graph `g`,
  and you call `g.add('Z', 'Y')`,
  and neither of these statements is true in view `v`:
  - `'Y'` has been marked as `done`.
  - `'Z'` has not yet been yielded by `get_ready`.
  then `v` is no longer "coherent".
  (If `'Y'` has been marked as `done`, then it's okay to make `'Z'` dependent on
  `'Y'` regardless of what state `'Z'` is in.  Likewise, if `'Z'` hasn't been yielded
  by `get_ready` yet, then it's okay to make `'Z'` dependent on `'Y'` regardless
  of what state `'Y'` is in.)
  Note that you can restore a view to coherence.  In this case,
  removing either `Y` or `Z` from `g` would resolve the incoherence
  between `v` and `g`, and `v` would start working again.
  Also note that you can have multiple views, in various states of iteration,
  and by modifying the graph you may cause some to become incoherent but not
  others.  Views are completely independent from each other.
  </dd></dl>

  <dl><dd>

  One minor complaint I have about Python regards inner classes.
  An "inner class" is a class defined inside another class.  And,
  well, inner classes seem kind of half-baked.  Unlike functions,
  inner classes don't get bound to the object.
  Consider this Python code:
  ```Python
  class Outer(object):
      def method(self):
          pass
      class Inner(object):
          def __init__(self):
              pass
  o = Outer()
  o.method()
  i = o.Inner()
  ```
  When `o.method` is called, Python automatically passes in the `o` object as the first parameter
  (generally called `self`).  In object-oriented lingo, `o` is *bound* to `method`, and indeed
  Python calls this object a *bound method*:
  ```
      >>> o.method
      <bound method Outer.method of <__main__.Outer object at 0x########>>
  ```
  But that doesn't happen when `o.Inner` is called.  (It *does* pass in
  a `self`, but in this case it's the newly-created `Inner` object.)
  There's just no built-in way for the `o.Inner` object being constructed
  to *automatically* get a reference to `o`.  If you need one, you must
  explicitly pass one in, like so:
  ```Python
  class Outer(object):
      def method(self):
          pass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
  o = Outer()
  o.method()
  i = o.Inner(o)
  ```
  This seems redundant.  You don't have to pass in `o` explicitly to method calls,
  why should you have to pass it in explicitly to inner classes?
  Well--now you don't have to!
  You just decorate the inner class with `@big.BoundInnerClass`,
  and `BoundInnerClass` takes care of the rest!

  Let's modify the above example to use our [`BoundInnerClass`](#boundinnerclasscls)
  decorator:
  ```Python
  from big import BoundInnerClass
  class Outer(object):
      def method(self):
          pass
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
  o = Outer()
  o.method()
  i = o.Inner()
  ```
  Notice that `Inner.__init__` now requires an `outer` parameter,
  even though you didn't pass in any arguments to `o.Inner`.
  When it's called, `o` is magically passed in to `outer`!
  Thanks, [`BoundInnerClass`](#boundinnerclasscls)!  You've saved the day!
  Decorating an inner class like this always adds a second positional
  parameter, after `self`.  And, like `self`, you don't have
  to use the name `outer`, you can use any name you like.
  (Although it's probably a good idea, for consistency's sakes.)

  Bound inner classes get slightly complicated when mixed with inheritance.
  It's not all that difficult, you merely need to obey the following rules:
  1. *A bound inner class can inherit normally from any unbound class.*
  2. *To subclass from a bound inner class while still inside the outer
  class scope, or when referencing the inner class from the outer class
  (as opposed to an instance of the outer class), you must actually
  subclass or reference `classname.cls`.*  This is because inside the
  outer class, the "class" you see is actually an instance of a
  [`BoundInnerClass`](#boundinnerclasscls) object.
  3. *All classes that inherit from a bound inner class must always call the
  superclass's `__init__`. You don't need to pass in the `outer` parameter;
  it'll be automatically passed in to the superclass's `__init__` as before.*
  4. *An inner class that inherits from a bound inner class, and which also
  wants to be bound to the outer object, should be decorated with
  [`BoundInnerClass`](#boundinnerclasscls).*
  5. *An inner class that inherits from a bound inner class, but doesn't
  want to be bound to the outer object, should be decorated with
  [`UnboundInnerClass`](#unboundinnerclasscls).*
  Restating the last two rules: every class that descends from any
  [`BoundInnerClass`](#boundinnerclasscls)
  should be decorated with either
  [`BoundInnerClass`](#boundinnerclasscls)
  or
  [`UnboundInnerClass`](#unboundinnerclasscls).
  Which one you use depends on what behavior you want--whether or
  not you want your inner subclass to automatically get the `outer`
  instance passed in to its `__init__`.
  Here's a simple example using inheritance with bound inner classes:
  ```Python
  from big import BoundInnerClass, UnboundInnerClass
  class Outer(object):
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
      @UnboundInnerClass
      class ChildOfInner(Inner.cls):
          def __init__(self):
              super().__init__()
  o = Outer()
  i = o.ChildOfInner()
  ```
  We followed the rules:
  * `Inner` inherits from object; since object isn't a bound inner class,
    there are no special rules about inheritance `Inner` needs to obey.
  * `ChildOfInner` inherits from `Inner.cls`, not `Inner`.
  * Since `ChildOfInner` inherits from a
    [`BoundInnerClass`](#boundinnerclasscls),
    it must be
    decorated with either [`BoundInnerClass`](#boundinnerclasscls)
    or [`UnboundInnerClass`](#unboundinnerclasscls).
    It doesn't want the outer object passed in, so it's decorated
    with [`UnboundInnerClass`](#unboundinnerclasscls).
  * `ChildOfInner.__init__` calls `super().__init__`.
  Note that, because `ChildOfInner` is decorated with
  [`UnboundInnerClass`](#unboundinnerclasscls),
  it doesn't take an `outer` parameter.  Nor does it pass in an `outer`
  argument when it calls `super().__init__`.  But when the constructor for
  `Inner` is called, the correct `outer` parameter is passed in--like magic!
  Thanks again, [`BoundInnerClass`](#boundinnerclasscls)!
  If you wanted `ChildOfInner` to also get the outer argument passed in to
  its `__init__`, just decorate it with [`BoundInnerClass`](#boundinnerclasscls)
  instead of
  [`UnboundInnerClass`](#unboundinnerclasscls),
  like so:
  ```Python
  from big import BoundInnerClass
  class Outer(object):
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
      @BoundInnerClass
      class ChildOfInner(Inner.cls):
          def __init__(self, outer):
              super().__init__()
              assert self.outer == outer
  o = Outer()
  i = o.ChildOfInner()
  ```
  Again, `ChildOfInner.__init__` doesn't need to explicitly
  pass in `outer` when calling `super.__init__`.
  You can see more complex examples of using inheritance with
  [`BoundInnerClass`](#boundinnerclasscls)
  (and [`UnboundInnerClass`](#unboundinnerclasscls))
  in the **big** test suite.

  * If you refer to a bound inner class directly from the outer *class,*
    rather than using the outer *instance,* you get the original class.
    This ensures that references to `Outer.Inner` are consistent; this
    class is also a base class of all the bound inner classes. Additionally,
    if you attempt to construct an instance of an unbound `Outer.Inner`
    class without referencing it via an instance, you must pass in the
    outer parameter by hand--just like you'd have to pass in the `self`
    parameter by hand when calling a method on the *class itself* rather
    than on an *instance* of the class.
  * If you refer to a bound inner class from an outer instance,
    you get a subclass of the original class.
  * Bound classes are cached in the outer object, which both provides
    a small speedup and ensures that `isinstance` relationships are
    consistent.
  * You must not rename inner classes decorated with either
    [`BoundInnerClass`](#boundinnerclasscls)
    or [`UnboundInnerClass`](#unboundinnerclasscls)!
    The implementation of
    [`BoundInnerClass`](#boundinnerclasscls)
    looks up
    the bound inner class in the outer object by name in several places.
    Adding aliases to bound inner classes is harmless, but the original
    attribute name must always work.
  * Bound inner classes from different objects are different classes.
    This is symmetric with bound methods; if you have two objects
    `a` and `b` that are instances of the same class,
    `a.BoundInnerClass != b.BoundInnerClass`, just as `a.method != b.method`.
  * The binding only goes one level deep; if you had an inner class `C`
    inside another inner class `B` inside a class `A`, the constructor
    for `C` would be called with the `B` object, not the `A` object.
  * Similarly, if you have a bound inner class `B` inside a class `A`,
    and another bound inner class `D` inside a class `C`, and `D`
    inherits from `B`, the constructor for `D` will be called with
    the `B` object but not the `A` object. When `D` calls `super().__init__`
    it'll have to fill in the `outer` parameter by hand.
  * There's a race condition in the implementation: if you access a
    bound inner class through an outer instance from two separate threads,
    and the bound inner class was not previously cached, the two threads
    may get different (but equivalent) bound inner class objects, and only
    one of those instances will get cached on the outer object.  This could
    lead to confusion and possibly cause bugs. For example, you could have
    two objects that would be considered equal if they were instances of
    the same bound inner class, but would not be considered equal if
    instantiated by different instances of that same bound inner class.
    There's an easy workaround for this problem: access the bound inner
    class from the `__init__` of the outer class, which should allow
    the code to cache the bound inner class instance before a second
    thread could ever get a reference to the outer object.
  </dd></dl>


  *2024/09/17*
  <dl><dd>
  Optimized
  [`split_delimiters`](#split_delimiterss-delimiters--state).
  The new version uses a much more efficient internal representation
  of how to react to the various delimiters when processing the text.
  Perfunctory `timeit` experiments suggest this new `split_delimiters`
  is maybe 5-6% faster than it was in 12.2.
  Minor breaking change: `split_delimiters` now consistently
  raises `SyntaxError` for mismatched delimiters.  (Previously it
  would sometimes raise `ValueError`.)
  </dd></dl>

  *2024/09/11*
  <dl><dd>
  * A minor semantic change to [`lines_strip_indent`](#lines_strip_indentli):
    when it encounters a whitespace-only line, it clips the line to *trailing*
    in the `LineInfo` object.  It used to clip such lines to *leading*.  But this
    changed `LineInfo.column_number` in a nonsensical way.
    This behavior is policy going forward: if a lines modifer function ever clips
    the entire line, it must clip it to *trailing* rather than *leading*.  It
    shouldn't matter one way or another, as whitespace-only lines arguably
    shouldn't have any explicit semantics.  But it makes intuitive sense to me
    that their empty line should be at column number 1, rather than 9 or 13
    or whatnot.  (Especially considering that with `lines_strip_indent` their
    indent value is synthetic anyway, inferred by looking ahead.)
  * Major cleanup to the lines modifier test suites.
  </dd></dl>

  *2024/09/07*
  <dl><dd>
  In fine **big** tradition, here's an update published
  immediately after a big release.
  Surprisingly, even though this is only a small update,
  it still adds *two* new packages to **big**: *metadata* and *version*.
  There's sadly one breaking change.

  <dl><dd>
  New package.
  A package containing metadata about **big** itself.
  Currently only contains one thing: *version*.
  </dd></dl>

  <dl><dd>
  New package.  A package for working with version information.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  The default value for `quotes` has changed.  Now it's
  what it should always have been: empty.  No quote marks
  are defined by default, which means the default behavior of
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  is now to simply truncate the line
  at the leftmost comment marker.
  Processing quote marks by default was *always* too opinionated
  for this function.  Consider: having `'` active as a quote
  marker meant that single-quotes need to be balanced,
  >    which means you can't process a line like this that only has one.
  Wish I'd figured this out before the release yesterday!  Hopefully
  this will only cause smiles, and no teeth-gnashing.
  </dd></dl>

  <dl><dd>
  New value.  A
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  object representing the current version of **big**.
  </dd></dl>

  <dl><dd>
  New class.
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  represents a version number.  You can
  construct them from [PEP 440](https://peps.python.org/pep-0440/)-compliant
  version strings, or specify them using keyword-only parameters.
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  objects are immutable, ordered, and hashable.
  </dd></dl>

  <dl><dd>
  *2024/09/06*
  Lots of changes this time!  Most of 'em are in the `big.text`
  module, particularly the `lines` and _lines modifier_
  functions.  But plenty of other modules got in on the fun too.
  **big** even has a new module: `deprecated`.  Deprecated
  functions and classes get moved into this module.  Note that
  the contents of `deprecated` are not automatically imported
  into `big.all`.
  The following functions and classes have breaking changes:
  <dl><dd>
  [`Delimiter`](#delimiterclose--escape-multilinetrue-quotingfalse)
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
  </dd></dl>
  These functions have been renamed:
  <dl><dd>
  `lines_filter_comment_lines` is now [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers)
  `lines_strip_comments` is now [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  `parse_delimiters` is now [`split_delimiters`](#split_delimiterss-delimiters--state)
  </dd></dl>
  **big** has five new functions:
  <dl><dd>
  [`combine_splits`](#combine_splitss-split_arrays)
  [`encode_strings`](#encode_stringso--encodingascii)
  [`LineInfo.clip_leading`](#lineinfoclip_leadingline-s)
  [`LineInfo.clip_trailing`](#lineinfoclip_trailingline-s)
  [`split_title_case`](split_title_cases--split_allcapstrue)
  </dd></dl>
  </dd></dl>
  Finally, here's an in-depth description of all changes
  in **big** 0.12, sorted by API name.

  <dl><dd>
  Extremely minor change!  Python's `bytes` and `str` objects
  don't agree on which ASCII characters represent line breaks.
  The `str` object obeys the Unicode standard, which means
  there are four:
      \n \v \f \r
  For some reason, Python's `bytes` object only supports two:
      \n \r
  I have no idea why this is.  We might fix it.  And if we do,
  **big** is ready.  It now calculates
  [`bytes_linebreaks`](#bytes_linebreaks)
  and
  [`bytes_linebreaks_without_crlf`](#bytes_linebreaks_without_crlf)
  on the fly to agree with Python.
  If either (or both) work as newline characters for the `splitlines`
  method on a `bytes` object, they'll automatically be inserted
  into these iterables of bytes linebreaks.
  </dd></dl>

  <dl><dd>
  New function. If you split a string two different ways,
  producing two arrays that sum to the original string,
  `combine_splits` will merge those splits together, producing
  a new array that splits in every place any of the two split
  arrays had a split.
  Example:
      >>> big.combine_splits("abcdefg", ['a', 'bcdef', 'g'], ['abc', 'd', 'efg'])
      ['a, 'bc', 'd', 'ef', 'g']
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  `Delimiter` is a simple data class, representing information about
  delimiters to
  [`split_delimiters`](#split_delimiterss-delimiters--state)
   (previously `parse_delimiters`).
  `split_delimiters` has changed, and some of those changes are
  reflected in the `Delimiter` object; also, some changes to `Delimiter`
  are simply better API choices.
  The old `Delimiter` object is deprecated but still available,
  as `big.deprecated.Delimiter`.  It should only be used with
  `big.deprecated.parse_delimiters`, which is also deprecated.
  `big.deprecated.Delimiter` will be removed when
  `big.deprecated.parse_delimiters` is removed, which will be
  no sooner than September 2025.
  Changes:
  * The first argument to the old `Delimiter` object was `open`,
    and was stored as the `open` attribute.  These have both been
    completely removed.  Now, the "open delimiter" is specified
    as a key in a dictionary of delimiters, mapping open delimiters
    to `Delimiter` objects.
  * The old `Delimiter` object had a boolean `backslash` attribute;
    if it was True, that delimiter allows escaping using a backslash.
    Now `Delimiter` has an `escape` parameter and attribute,
    specifying the escape string you want to use inside that
    set of delimiters.
  * `Delimiter` also now has two new attributes, `quoting` and
    `multiline`.  These default to `False` and `True` respectively;
    you can specify values for these with keyword-only arguments
    to the constructor.
  * The new `Delimiter` object is read-only after construction,
    and is hashable.
  </dd></dl>

  <dl><dd>
  Slightly liberalized the types it accepts.  It previously
  required `o` to be a collection; now `o` can be a `bytes`
  or `str` object.  Also, it now explicitly supports `set`.
  </dd></dl>

  <dl><dd>
  Minor behavior change.  If the `o` you pass in is a `float`,
  or can be converted to `float` (but couldn't be converted directly
  to an `int`), `get_int_or_float` will experimentally convert that
  `float` to an `int`.  If the resulting `int` compares equal to that
  `float`, it'll return the `int`, otherwise it'll return the `float`.
  For example, `get_int_or_float("13.5")` still returns `13.5`
  (a `float`), but `get_int_or_float("13.0")` now returns `13`
  (an `int`).  (Previously, `get_int_or_float("13.0")` would
  have returned `13.0`.)
  This better represents the stated aesthetic of the function--it
  prefers ints to floats.  And since the int is exactly equal to
  the float, I assert this is completely backwards compatible.
  </dd></dl>

  <dl><dd>
  Minor updates to the documentation and to the text of some exceptions.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  Breaking change: the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  constructor has a
  new `lines` positional parameter, added *in front of*
  the existing positional parameters.  This new first argument
  should be the  `lines` iterator that yielded this
  `LineInfo` object.  It's stored in the `lines` attribute.
  (Why this change?  The `lines` object contains information
  needed by the lines modifiers, for example `tab_width`.)
  Minor optimization:
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects previously had many
  optional fields, which might or might not be added
  dynamically.  Now all fields are pre-added.  (This makes
  the CPython 3.13 runtime happier; it really wants you to
  set *all* your class's attributes in its `__init__`.)
  Minor breaking change: the original string stored in the
  `line` attribute now includes the linebreak character, if any.
  This means concatenating all the `info.line` strings
  will reconstruct the original `s` passed in to `lines`.
  New feature: while some methods used to update the `leading`
  attribute when they clipped leading text from the line,
  the "lines modifiers" are now very consistent about updating
  `leading`, and the new symmetrical attribute `trailing`.
  New feature:
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  now has an `end` attribute,
  which contains the end-of-line character that ended this line.
  These three attributes allow us to assert a new invariant:
  as long as you _modify_ the contents of `line` (e.g.
  turning tabs into spaces),
      info.leading + line + info.trailing + info.end == info.line
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects now always have these attributes:
    * `lines`, which contains the base lines iterator.
    * `line`, which contains the original unmodified line.
    * `line_number`, which contains the line number of
      this line.
    * `column_number`, which contains the starting column
      number of the first character of this line.
    * `indent`, which contains the indent level of the
      line if computed, and `None` otherwise.
    * `leading`, which contains the string stripped from
      the beginning of the line.  Initially this is the
      empty string.
    * `trailing`, which contains the string stripped from
      the end of the line.  Initially this is the
      empty string.
    * `end`, which is the end-of-line character
      that ended the current line.  For the last line yielded,
      `info.end` will always be the empty string.  If the last
      character of the text split by `lines` was an end-of-line
      character, the last `line` yielded will be the empty string,
      and `info.end` will also be the empty string.
    * `match`, which contains a `Match` object if this line
      was matched with a regular expression, and `None` otherwise.

  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  also has two new methods:
  [`LineInfo.clip_leading`](#lineinfoclip_leadingline-s)
  and
  [`LineInfo.clip_trailing(line, s)`](#lineinfoclip_trailingline-s).
  These methods clip a leading or
  trailing substring from the current `line`, and transfer
  it to the relevant field in
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  (either `leading` or
  `trailing`).  `clip_leading` also updates the `column_number`
  attribute.
  The name "clip" was chosen deliberately to be distinct from "strip".
  "strip" functions on strings remove substrings and throws them away;
  my "clip" functions on strings removes substrings and puts them
  somewhere else.
  </dd></dl>

  <dl><dd>
  `lines_filter_comment_lines` has been renamed to
  [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers).
  For backwards compatibility, the function
  is also available under the old name; this old name will
  eventually be removed, but not before September 2025.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  New name for `lines_filter_comment_lines`.
  Correctness improvements:
  [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers)
  now enforces that single-quoted strings can't span lines,
  and multi-quoted strings must be closed before the end of
  the last line.
  Minor optimization: for every line, it used to `lstrip` a copy of
  the line, then use a regular expression to see if the line started
  with one of the comment characters.  Now the regular expression
  itself skips past any leading whitespace.
  </dd></dl>

  <dl><dd>
  New feature: [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0-matchmatch)
  has always used `re.search` to examine
  the lines yielded.  It now writes the result to `info.match`.
  (If you pass in `invert=True` to `lines_grep`, `lines_grep`
  still writes to the `match` attribute--but it always writes `None`.)
  If you want to write the `re.Match` object to another attribute,
  pass in the name of that attribute to the keyword-only
  parameter `match`.
  </dd></dl>

  <dl><dd>
  New feature:
  [`lines_rstrip`](#lines_rstripli-separatorsnone)
  and
  [`lines_strip`](#lines_stripli-separatorsnone)
  now both accept a
  `separators` argument; this is an iterable of separators,
  like the argument to
  [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse).
  The default value of `None` preserves the previous behavior,
  stripping whitespace.
  </dd></dl>

  <dl><dd>
  New feature:
  [`lines_sort`](#lines_sortli--keynone-reversefalse)
  now accepts a `key` parameter,
  which is used as the `key` argument for `list.sort`.
  The value passed in to `key` is the `(info, line)` tuple
  yielded by the upstream iterator.  The default value preserves
  the previous behavior, sorting by the `line` (ignoring the
  `info`).
  </dd></dl>

  <dl><dd>
  This function has been renamed
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  and
  rewritten, see below.  The old deprecated version will be
  available at `big.deprecated.lines_strip_comments` until at
  least September 2025.
  Note that the old version of `line_strip_comments` still uses
  the current version of
  [`LineInfo`,](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  so use of this deprecated
  function is still exposed to those breaking changes.
  (For example, `LineInfo.line` now includes the linebreak character
  that terminated the current line, if any.)
  </dd></dl>

  <dl><dd>
  Bugfix:
  [`lines_strip_indent`](#lines_strip_indentli)
  previously required
  whitespace-only lines to obey the indenting rules, which was
  a mistake.  My intention was always for `lines_strip_indent`
  to behave like Python, and that includes not really caring
  about the intra-line-whitespace for whitespace-only
  lines.  Now `lines_strip_indent` behaves more like Python:
  a whitespace-only line behaves as if it has
  the same indent as the previous line.  (Not that the
  indent value of an empty line should matter--but this
  behavior is how you'd intuitively expect it to work.)
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  is the new name for the old
  `lines_strip_comments` lines modifier function.  It's also
  been completely rewritten.
  Changes:
  * The old function required quote marks and the escape string
    to be single characters. The new function allows quote marks
    and the escape string to be of any length.
  * The old function had a slightly-smelly `triple_quotes` parameter
    to support multiline strings.  The new version supports separate
    parameters for single-line quote marks (`quotes`)  and multiline
    quote marks (`multiline_quotes`).
  * The `backslash` parameter has been renamed to `escape`.
  * The `rstrip` parameter has been removed.  If you need to
    rstrip the line after stripping the comment, wrap your
    `lines_strip_line_comments` call with a
    [`lines_rstrip`](#lines_rstripli-separatorsnone)
    call.
  * The old function didn't enforce that strings shouldn't
    span lines--single-quoted and triple-quoted strings behaved
    identically.  The new version raises `SyntaxError` if quoted
    strings using non-multiline quote marks contain newlines.
  (`lines_strip_line_comments` has always been implemented using
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state);
  this is why it now supports multicharacter
  quote marks and escape strings.  It also benefits from the
  new optimizations in `split_quoted_strings`.)
  </dd></dl>

  <dl><dd>
  Minor optimizations.
  [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
  used to locally define a
  new generator function, then call it and return the generator.
  I promoted the generator function to module level, which means
  we no longer rebind it each time `multisplit` is called.  As
  a very rough guess, this can be as much as a 10% speedup for
  `multisplit` run on very short workloads.  (It's also *never*
  slower.)
  I also applied this same small optimization to several other
  functions in the `text` module.  In particular,
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  was binding functions inside a loop (!!).  (Dumb, huh!)
  These local functions are still bound inside `merge_columns`,
  but now at least they're outside the loop.
  Another minor speedup for `multisplit`: when `reverse=True`,
  it used to reverse the results *three times!*  `multisplit`
  now explicitly observes and manages the reversed state of the
  result to avoid needless reversing.
  </dd></dl>

  <dl><dd>
  This function has been renamed
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  and rewritten,
  see below.  The old version is still available, using the name
  `big.deprecated.parse_delimiters` module, and will be available
  until at least September 2025.
  </dd></dl>

  <dl><dd>
  Code cleanups both in the implementation and the test suite,
  including one minor semantic change.
  Cleaned up `Scheduler._next`, the internal method call
  that implements the heart of the scheduler.  The only externally
  visible change: the previous version would call `sleep(0)` every
  time it yielded an event.  On modern operating systems this should
  yields the rest of the current thread's current time slice back
  to the OS's scheduler.  This can make multitasking smoother,
  particularly in Python programs.  But this is too opinionated for
  library code--if you want a `sleep(0)` there, by golly, you can
  call that yourself when the `Scheduler` object yields to you.
  I've restructured the code and eliminated this extraneous `sleep(0)`.
  Also, rewrote big chunks of the test suite (`tests/test_scheduler.py`).
  The multithreaded tests are now much better synchronized, while
  also becoming easier to read.  Although it seems intractable to
  purge *all* race conditions from the test suite, this change has
  removed most of them.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  is the new name for the old `parse_delimiters`
  function.  The function has also been completely re-tooled and
  re-written.
  Changes:
  * `parse_delimiters` took an iterable of `Delimiters`
    objects, or strings of length 2.  `split_delimiters`
    takes a dictionary mapping open delimiter strings to
    `Delimiter` objects, and `Delimiter` objects no
    longer have an "open" attribute.
  * `split_delimiters` now accepts an `state` parameter,
    which specifies the initial state of nested delimiters.
  * `split_delimiters` no longer cares if there were unclosed
    open delimiters at the end of the string.  (It used to
    raise `ValueError`.)  This includes quote marks; if you
    don't want quoted strings to span multiple lines, it's up
    to you to detect it and react (e.g. raise an exception).
  * The internal implementation has changed completely.
    `parse_delimiters` manually parsed the input string
    character by character.  `split_delimiters` uses
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    so it zips past the uninteresting characters and only examines
    the delimiters and escape characters.  It's always faster,
    except for some trivial calls (which are fast enough anyway).
  * Another benefit of using `multisplit`: open delimiters,
    close delimiters, and the escape string may now all be
    any nonzero length.  (In the face of ambiguity,
    `split_delimiters` will always choose the longer delimiter.)
  See also changes to `Delimiter`.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
  has been completely re-tooled and
  re-written.  The new API is simpler, easier to understand,
  and conceptually clarified.  It's a major upgrade!
  Changes:
  * The value it yields is different:
    * The old version yielded `(is_quote, segment)`, where
      `is_quote` was a boolean value indicating whether or not
      `segment` was quoted.  If `segment` was quoted, it began
      and ended with (single character) quote marks.  To reassemble
      the original string, join together all the `segment` strings
      in order.
    * The new version yields `(leading_quote, segment, trailing_quote)`,
      where `leading_quote` and `trailing_quote` are either matching
      quote marks or empty.  If they're true values, the `segment`
      string is inside the quotes.  To reassemble the original string,
      join together *all* the yielded strings in order.
  * The `backslash` parameter has been replaced by a new parameter,
    `escape`.  `escape` allows specifying the escape string, which
    defaults to '\\' (backslash).  If you specify a false value,
    there will be no escape character in strings.
  * By default `quotes` only contains `'` (single-quote)
    and `"` (double-quote).  The previous version also
    recognized `"""` and `'''` as multiline quote marks
    by default; this is no longer true, as it's too
    opinionated and Python-specific.
  * The old version didn't actually distinguish between
    single-quoted strings and triple-quoted strings.  It
    simply didn't care whether or not there were newlines
    inside quoted strings.  The new version raises a
    `SyntaxError` if there's a newline character inside
    a string delimited with a quote marker from `quotes`.
  * The old version accepted a stinky `triple_quotes` parameter.
    That's been removed in favor of a new parameter,
    `multiline_quotes`.  `multiline_quotes` is like `quotes`,
    except that newline characters are allowed inside their
    quoted strings.
  * `split_quoted_string` accepts another new parameter,
    `state`, which sets the initial state of quoting.
  * Thd old implementation of `split_quoted_string` used a
    hand-coded parser, manually analyzing each character in
    the input text.  Now it uses
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    so it only bothers to examine
    the interesting substrings.  `multisplit` has a large
    startup cost the first time you use a particular set of
    iterators, but this information is cached for subsequent calls.
    Bottom line, the new version is much faster
    for larger workloads.  (It can be slower for trivial
    examples... where speed doesn't matter anyway.)
  * Another benefit of switching to `multisplit`: `quotes`
    now supports quote delimiters and an escape string
    of any nonzero length.  In the case of ambiguity--if
    more than one quote delimiter matches at a
    time--`split_quoted_string` will always choose the
    longer delimiter.
  </dd></dl>

  <dl><dd>
  New function.
  [`split_title_case`](split_title_cases--split_allcapstrue)
  splits a string at word boundaries,
  assuming the string is in "TitleCase".
  </dd></dl>

  <dl><dd>
  Small performance upgrade for
  [`StateManager`.](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
  observers.
  `StateManager` always uses a copy of the observer
  list (specifically, a tuple) when calling the observers; this
  means it's safe to modify the observer list at any time.
  `StateManager` used to always make a fresh copy every time you
  called an event; now it uses a cached copy, and only recomputes
  the tuple when the observer list changes.
  (Note that it's not thread-safe to modify the observer list
  from one thread while also dispatching events in another.
  Your program won't crash, but the list of observers called
  may be unpredictable based on which thread wins or loses the
  race.  But this has always been true.  As with many libraries,
  the `StateManager` API leaves locking up to you.)
  </dd></dl>
  _p.s. I'm getting close to declaring big as being version 1.0._
  _I don't want to do it until I'm done revising the APIs._
  _p.p.s. Updated copyright notices to 2024._
  _p.p.p.s. Yet again I thank Eric V. Smith for his willingness to humor me
  in my how-many-parameters-could-dance-on-the-head-of-a-pin API theological
  discussions._
  </dd></dl>

  <dl><dd>
  *released 2023/09/19*
  * Breaking change: renamed almost all the old `whitespace` and `newlines` tuples.
    Worse yet, one symbol has the same name but a *different value:* `ascii_whitespace`!
    I've also changed the suffix `_without_dos` to the more accurate and intuitive
    `_without_crlf`, and similarly changed `newlines` to `linebreaks`.
    Sorry for all the confusion.  This resulted from a lot of research into whitespace
    and newline characters, in Python, Unicode, and ASCII; please see the new deep-dive
    [**Whitespace and line-breaking characters in Python and big**](#whitespace-and-line-breaking-characters-in-python-and-big)
    to see what all the fuss is about.  Here's a summary of all the
    changes to the whitespace tuples:
          RENAMED TUPLES (old name -> new name)
            ascii_newlines               -> bytes_linebreaks
            ascii_whitespace             -> bytes_whitespace
            newlines                     -> linebreaks
            ascii_newlines_without_dos   -> bytes_linebreaks_without_crlf
            ascii_whitespace_without_dos -> bytes_whitespace_without_crlf
            newlines_without_dos         -> linebreaks_without_crlf
            whitespace_without_dos       -> whitespace_without_crlf
          REMOVED TUPLES
            utf8_newlines
            utf8_whitespace
            utf8_newlines_without_dos
            utf8_whitespace_without_dos
          UNCHANGED TUPLES (same name, same meaning)
            whitespace
          NEW TUPLES
            ascii_linebreaks
            ascii_whitespace
            str_linebreaks
            str_whitespace
            unicode_linebreaks
            unicode_whitespace
            ascii_linebreaks_without_crlf
            ascii_whitespace_without_crlf
            str_linebreaks_without_crlf
            str_whitespace_without_crlf
            unicode_linebreaks_without_crlf
            unicode_whitespace_without_crlf
  * Changed
    [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
    implementation to use `StateManager`.
    (No API or semantic changes, just an change to the internal implementation.)
  * New function in the [`big.text`](#bigtext) module: [`encode_strings`,](#encode_stringso--encodingascii)
    which takes a container object containing `str` objects and returns an equivalent object
    containing encoded versions of those strings as `bytes`.
  * When you call
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    with a type mismatch
    between 's' and 'separators', the exception it raises
    now includes the values of 's' and 'separators'.
  * Added more tests for `big.state` to exercise all the string arguments
    of `accessor` and `dispatch`.
  * The exhaustive
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    tester now lets you
    specify test cases as cohesive strings, rather
    than forcing you to split the string manually.
  * The exhaustive
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    tester is better at
    internally verifying that it's doing the right
    thing.  (There are some internal sanity checks,
    and those are more accurate now.)
  * Whoops!  The name of the main class in [`big.state`](#bigstate) is
    [`StateManager`.](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
    I accidentally wrote `StateMachine` instead in the docs... several times.
  * Originally the
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    parameter 'separators'
    was required.  I changed it to optional a while ago,
    with a default of `None`.  (If you pass in `None`
    it uses [`big.str_whitespace`](#str_whitespace) or [`big.bytes_whitespace`](#bytes_whitespace),
    depending on the type of `s`.)  But the documentation
    didn't reflect this change until... now.
  * Improved the prose in
    [**The `multi-` family of string functions** deep-dive.](#The-multi--family-of-string-functions)
    Hopefully now it does a better job of selling `multisplit` to the reader.
  * The usual smattering of small doc fixes and improvements.
  My thanks again to Eric V. Smith for his willingness to consider and discuss these
  issues.  Eric is now officially a contributor to **big,** increasing the project's
  [bus factor](https://en.wikipedia.org/wiki/Bus_factor) to two.  Thanks, Eric!

  <dl><dd>
  *released 2023/09/04*
  * Added the new [`big.state`](#bigstate) module, with its exciting
    [`StateManager`](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
    class!
  * [`int_to_words`](#int_to_wordsi--flowerytrue-ordinalfalse)
    now supports the new `ordinal` keyword-only parameter, to produce
    *ordinal* strings instead of *cardinal* strings.  (The number 1
    as a *cardinal* string is `'one'`, but as an *ordinal* string is `'first'`).
  * Added the [`pure_virtual`](#pure_virtual) decorator to [`big.builtin`](#bigbuiltin).
  * The documentation is now much prettier!  I finally discovered a syntax
    I can use to achieve a proper indent in Markdown, supported by both
    GitHub and PyPI. You simply nest the text you want indented inside
    an HTML description list as the description text, and skip the
    description item (`<dl><dd>`).  Note that you need a blank
    line after the `<dl><dd>` line, or else Markdown will ignore the
    markup in the following paragraph.  Thanks to Hugo van Kemenade
    for his help confirming this!  Oh, and, Hugo also fixed the image markup
    so the **big** banner displays properly on PyPI.  Thanks, Hugo!
  </dd></dl>

  <dl><dd>
  *released 2023/07/22*
  Extremely minor release.  No new features or bug fixes.
  * Fixed coverage, now back to the usual 100%.
    (This just required changing the tests, which
    *didn't* find any new bugs.)
  * Made the tests for [`Log`](#log-clocknone)
    deterministic.  They now use a fake clock
    that always returns the same values.
  * Added GitHub Actions integration.  Tests and
    coverage are run in the cloud after every checkin.
    Thanks to [Dan Pope](https://github.com/lordmauve)
    for gently walking me through this!
  * Fixed metadata in the `pyproject.toml` file.
  * Added badges for testing, coverage,
    and supported Python versions.
  </dd></dl>

  <dl><dd>
  *released 2023/06/28*
  * Added the new [`big.log`](#biglog) module, with its new
    [`Log`](#log-clocknone) class!
    I wrote this for another project--but it turned out so nice
    I just had to add it to **big**!
  </dd></dl>

  <dl><dd>
  *released 2023/06/15*
  * Bugfix!  If an outer class `Outer` had an inner class `Inner`
    decorated with `@BoundInnerClass`, and `o` is an instance of
    `Outer`, and `o` evaluated to false in a boolean context,
    `o.Inner` would be the *unbound* version of `Inner`.  Now
    it's the bound version, as is proper.
  * Modified `tests/test_boundinnerclasses.py`:
      - Added regression test for the above bugfix (of course!).
      - It now takes advantage of that newfangled "zero-argument `super`".
      - Added testing of an unbound subclass of an unbound subclass.
  </dd></dl>

  <dl><dd>
  *released 2023/06/11*
  * Added
    [`int_to_words`](#int_to_wordsi--flowerytrue-ordinalfalse).
  * All tests now insert the local **big** directory
    onto `sys.path`, so you can run the tests on your
    local copy without having to install.  Especially
    convenient for testing with old versions of Python!
  > *Note:* tomorrow, **big** will be one year old!
  </dd></dl>

  <dl><dd>
  *released 2023/05/19*
  * Convert all iterator functions to use my new approach:
    instead of checking arguments inside the iterator,
    the function you call checks arguments, then has a
    nested iterator function which it runs and returns the
    result.  This means bad inputs raise their exceptions
    at the call site where the iterator is constructed,
    rather than when the first value is yielded by the iterator!
  </dd></dl>

  <dl><dd>
  *released 2023/05/19*
  * Added
    `parse_delimiters` (ed: now [`split_delimiters`](#split_delimiterss-delimiters--state))
    and
    [`Delimiter`.](#delimiteropen-close--backslashfalse-nestedtrue)
  </dd></dl>

  <dl><dd>
  *released 2023/05/18*
  * Major retooling of `str` and `bytes` support in `big.text`.
    * Functions in `big.text` now uniformly accept `str` or `bytes`
      or a subclass of either.  See the
      [Support for bytes and str](#Support-for-bytes-and-str) section
      for how it works.
    * Functions in `big.text` are now more consistent about raising
      `TypeError` vs `ValueError`.  If you mix `bytes` and `str`
      objects together in one call, you'll get a `TypeError`, but
      if you pass in an empty iterable (of a correct type) where
      a non-empty iterable is required you'll get a `ValueError`.
      `big.text` generally tries to give the `TypeError` higher
      priority; if you pass in a value that fails both the type
      check and the value check, the `big.text` function will raise
      `TypeError` first.
  * Major rewrite of
    [`re_rpartition`.](#re_rpartitiontext-pattern-count1--flags0)
    I realized it had the same "reverse mode" problem that
    I fixed in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    back in version **0.6.10**: the regular expression should really
    search the string in "reverse mode", from right to left.
    The difference is whether the regular
    expression potentially matches against overlapping strings.
    When in forwards mode, the regular expression should prefer
    the *leftmost* overlapping match, but in reverse mode it
    should prefer the *rightmost* overlapping match.  Most of the
    time this produces the same list of matches as you'd
    find searching the string forwards--but sometimes the matches come
    out *very* different.
    This was way harder to fix with `re_rpartition` than with `multisplit`,
    because Python's `re` module only supports searching forwards.
    I have to emulate reverse-mode searching by manually checking for
    overlapping matches and figuring out which one(s) to keep--a *lot* of
    work!  Fortunately it's only a minor speed hit if you don't have
    overlapping matches.  (And if you *do* have overlapping matches,
    you're probably just happy `re_rpartition` now produces correct
    results--though I did my best to make it performant anyway.)
    In the future, **big** will probably add support for the
    PyPI package `regex`, which reimplements Python's `re` module
    but adds many features... including reverse mode!
  * New function:
    [`reversed_re_finditer`.](#reversed_re_finditerpattern-string-flags0)
    Behaves almost identically to the Python
    standard library function `re.finditer`, yielding
    non-overlapping matches of `pattern` in `string`.  The difference
    is, `reversed_re_finditer` searches `string` from right to left.
    (Written as part of the
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0)
    rewrite mentioned above.)
  * Added `apostrophes`, `double_quotes`,
    `ascii_apostrophes`, `ascii_double_quotes`,
    `utf8_apostrophes`, and `utf8_double_quotes`
    to the `big.text` module.  Previously the first
    four of these were hard-coded strings inside
    [`gently_title`.](#gently_titles-apostrophesnone-double_quotesnone)
    (And the last two didn't exist!)
  * Code cleanup in `split_text_with_code`, removed redundant code.
    I think it has about the same number of `if` statements; if anything
    it might be slightly faster.
  * Retooled
    [`re_partition`](#re_partitiontext-pattern-count1--flags0)
    and
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0)
    slightly, should now be very-slightly faster.  (Well, `re_rpartition`
    will be slower if your pattern finds overlapping matches.  But at
    least now it's correct!)
  * Lots and lots of doc improvements, as usual.
  </dd></dl>

  <dl><dd>
  *released 2023/03/13*
  * Tweaked the implementation of
    [`multisplit`.](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    Internally, it does the
    string splitting using `re.split`, which returns a `list`.  It used
    to iterate over the list and yield each element.  But that meant keeping
    the entire list around in memory until `multisplit` exited.  Now,
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    reverses the list, pops off the final element, and yields
    that.  This means
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    drops all references to the split strings
    as it iterates over the string, which may help in low-memory situations.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/03/11*
  * Breaking changes to the
    [`Scheduler`](#schedulerregulatordefault_regulator):
    * It's no longer thread-safe by default, which means it's much faster
      for non-threaded workloads.
    * The lock has been moved out of the
      [`Scheduler`](#schedulerregulatordefault_regulator)
      object and into the
      [`Regulator`](#regulator).  Among other things, this
      means that the
      [`Scheduler`](#schedulerregulatordefault_regulator)
      constructor no longer takes a `lock` argument.
    * [`Regulator`](#regulator) is now an abstract base class.
      `big.scheduler` also provides two concrete implementations:
      [`SingleThreadedRegulator`](#singlethreadedregulator)
      and
      [`ThreadSafeRegulator`](#threadsaferegulator).
    * [`Regulator`](#regulator) and
      [`Event`](#eventscheduler-event-time-priority-sequence)
      are now defined in the `big.scheduler` namespace.  They were
      previously defined inside the `Scheduler` class.
    * The arguments to the
      [`Event`](#eventscheduler-event-time-priority-sequence)
      constructor were rearranged.  (You shouldn't care, as you
      shouldn't be manually constructing
      [`Event`](#eventscheduler-event-time-priority-sequence)
      objects anyway.)
    * The `Scheduler` now guarantees that it will only call `now` and `wake`
      on a `Regulator` object while holding that `Regulator`'s lock.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/03/09*
  * Retooled
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    and
    [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
    argument verification code.  Both functions now consistently check all
    their inputs, and use consistent error messages when raising an exception.
  </dd></dl>

  <dl><dd>
  *released 2023/03/09*
  * Fixed a minor crashing bug in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse):
    if you passed in a *list* of separators (or `separators`
    was of any non-hashable type), and `reverse` was true,
    `multisplit` would crash.  It used `separators` as a key
    into a dict, which meant `separators` had to be hashable.
  * [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    now verifies that the `s` passed in is either `str` or `bytes`.
  * Updated all copyright date notices to 2023.
  * Lots of doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/02/26*
  * Fixed Python 3.6 support! Some equals-signs-in-f-strings and some
    other anachronisms had crept in.  0.6.16 has been tested on all
    versions from 3.6 to 3.11 (as well as having 100% *coverage*).
  * Made the `dateutils` package an optional dependency.  Only one function
    needs it, [`parse_timestamp_3339Z()`](#parse_timestamp_3339zs--timezonenone).
  * Minor cleanup in [`PushbackIterator()`](#pushbackiteratoriterablenone).
    It also uses slots now, which should make it a bit faster.
  </dd></dl>

  <dl><dd>
  *released 2023/01/07*
  * Added the new functions
    [`datetime_ensure_timezone(d, timezone)`](#datetime_ensure_timezoned-timezone) and
    [`datetime_set_timezone(d, timezone)`](#datetime_set_timezoned-timezone).
    These allow you to ensure or explicitly set a timezone on a `datetime.datetime`
    object.
  * Added the `timezone` argument to
    [`parse_timestamp_3339Z()`](#parse_timestamp_3339zs--timezonenone).
  * [`gently_title()`](#gently_titles-apostrophesnone-double_quotesnone)
    now capitalizes the first letter after a left parenthesis.
  * Changed the secret `multirpartition` function slightly.  Its `reverse`
    parameter now means to un-reverse its reversing behavior.  Stated
    another way, `multipartition(reverse=X)` and `multirpartition(reverse=not X)`
    now do the same thing.
  </dd></dl>

  <dl><dd>
  *released 2022/12/11*
  * Improved the text of the `RuntimeError` raised by `TopologicalSorter.View`
    when the view is incoherent.  Now it tells you exactly what nodes are
    conflicting.
  * Expanded the deep dive on `multisplit`.
  </dd></dl>

  <dl><dd>
  *released 2022/12/11*
  * Changed [`translate_filename_to_exfat(s)`](#translate_filename_to_exfats)
    behavior: when modifying a string with a colon (`':'`) *not* followed by
    a space, it used to convert it to a dash (`'-'`).  Now it converts the
    colon to a period (`'.'`), which looks a little more natural.  A colon
    followed by a space is still converted to a dash followed by a space.
  </dd></dl>

  <dl><dd>
  *tagged 2022/12/04*
  * Bugfix: When calling
    [`TopologicalSorter.print()`](#topologicalsorterprintprintprint),
    it sorts the list of nodes, for consistency's sakes and for ease of reading.
    But if the node objects don't support `<` or `>` comparison,
    that throws an exception.  `TopologicalSorter.print()` now catches
    that exception and simply skips sorting.  (It's only a presentation thing anyway.)
  * Added a secret (otherwise undocumented!) function: `multirpartition`,
    which is like
    [`multipartition`](#multipartitions-separators-count1--reverseFalse-separateTrue)
    but with `reverse=True`.
  * Added the list of conflicted nodes to the "node is incoherent"
    exception text.
  > *Note:* although version **0.6.12** was tagged, it was never packaged for release.
  </dd></dl>

  <dl><dd>
  *tagged 2022/11/13*
  * Changed the import strategy.  The top-level **big** module used
    to import all its child modules, and `import *` all the symbols
    from all those modules.  But a friend (hi Mark Shannon!) talked
    me out of this.  It's convenient, but if a user doesn't care about
    a particular module, why make them import it.  So now the top-level
    **big** module contains nothing but a version number, and you
    can either import just the submodules you need, or you can import
    **big.all** to get all the symbols (like **big** itself used to do).
  > *Note:* although version **0.6.11** was tagged, it was never packaged for release.
  </dd></dl>

  <dl><dd>
  *released 2022/10/26*
  * All code changes had to do with
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse):
      * Fixed a subtle bug.  When splitting with a separator that can overlap
        itself, like `' x '`, `multisplit` will prefer the *leftmost* instance.
        But when `reverse=True`, it must prefer the *rightmost* instance.
        Thanks to Eric V. Smith for suggesting the clever "reverse everything,
        call `re.split`, and un-reverse everything" approach.  That let me
        fix this bug while still implementing on top of `re.split`!
      * Implemented `PROGRESSIVE` mode for the `strip` keyword.  This behaves
        like `str.strip`: when splitting, strip on the left, then start splitting.
        If we don't exhaust `maxsplit`, strip on the right; if we *do* exhaust
        `maxsplit`, *don't* strip on the right.  (Similarly for `str.rstrip`
        when `reverse=True`.)
      * Changed the default for `strip` to `False`.  It used to be
        `NOT_SEPARATE`.  But this was too surprising--I'd forget that it
        was the default, and turning on `keep` wouldn't return everything I
        thought I should get, and I'd head off to debug `multisplit`, when in
        fact it was behaving as specified.  The Principle Of Least Surprise
        tells me that `strip` defaulting to `False` is less surprising.
        Also, maintaining the invariant that all the keyword-only parameters
        to `multisplit` default to `False` is a helpful mnemonic device in
        several ways.
      * Removed `NOT_SEPARATE` (and the not-yet-implemented `STR_STRIP`)
        modes for `strip`.  They're easy to implement yourself, and this
        removes some surface area from the already-too-big
        `multisplit` API.
  * Modernized `pyproject.toml` metadata to make `flit` happier.  This was
    necessary to ensure that `pip install big` also installs its dependencies.
  </dd></dl>

  <dl><dd>
  *released 2022/10/16*
  * Renamed two of the three freshly-added lines modifier functions:
    `lines_filter_contains` is now
    [`lines_containing`](#lines_containingli-s--invertfalse),
    and `lines_filter_grep` is now
    [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0-matchmatch).
  </dd></dl>

  <dl><dd>
  *released 2022/10/16*
  * Added three new lines modifier functions
    to the [`text`](#bigtext) module:
    `lines_filter_contains`,
    `lines_filter_grep`,
    and
    [`lines_sort`.](#lines_sortli--keynone-reversefalse)
  * [`gently_title`](#gently_titles-apostrophesnone-double_quotesnone)
    now accepts `str` or `bytes`.  Also added the `apostrophes` and
    `double_quotes` arguments.
  </dd></dl>

  <dl><dd>
  *released 2022/10/14*
  * Fixed a bug in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse).
    I thought when using `keep=AS_PAIRS` that it shouldn't ever emit a 2-tuple
    containing just empty strings--but on further reflection I've realized that
    that's correct.  This behavior is now tested and documented, along with
    the reasoning behind it.
  * Added the `reverse` flag to
    [`re_partition`](#re_partitiontext-pattern-count1--flags0-reversefalse).
  * `whitespace_without_dos` and `newlines_without_dos` still had the DOS
    end-of-line sequence in them!  Oops!
      * Added a unit test to check that.  The unit test also ensures that
        `whitespace`, `newlines`, and all the variants (`utf8_`, `ascii_`,
        and `_with_dos`) exactly match the set of characters Python considers
        whitespace and newline characters.
  * Lots more documentation and formatting fixes.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  * Added the new [`itertools`](#bigitertools) module, which so far only contains
    [`PushbackIterator`](#pushbackiteratoriterablenone).
  * Added
    `lines_strip_comments` [ed: now [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
    and
    [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
    to the
    [`text`](#bigtext)
    module.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  * I realized that [`whitespace`](#whitespace) should contain the DOS end-of-line
    sequence (`'\r\n'`), as it should be considered a single separator
    when splitting etc.  I added that, along with [`whitespace_no_dos`](#whitespace),
    and naturally [`utf8_whitespace_no_dos`](#whitespace) and
    [`ascii_whitespace_no_dos`](#whitespace) too.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  A **big** upgrade!
  * Completely retooled and upgraded
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    and added
    [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
    and
    [`multipartition`,](#multipartitions-separators-count1--reverseFalse-separateTrue)
    collectively called
    [**The `multi-` family of string functions.**](#The-multi--family-of-string-functions)
    (Thanks to Eric Smith for suggesting
    [`multipartition`!](#multipartitions-separators-count1--reverseFalse-separateTrue)
    Well, sort of.)
    * `[`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)`
      now supports five (!) keyword-only parameters, allowing the caller
      to tune its behavior to an amazing degree.
    * Also, the original implementation of
      `[`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)`
      got its semantics a bit wrong; it was inconsistent and maybe a little buggy.
    * [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
      is like `str.strip` but accepts an iterable of
      separator strings.  It can strip from the left, right, both, or
      neither (in which case it does nothing).
    * [`multipartition`](#multipartitions-separators-count1--reverseFalse-separateTrue)
      is like `str.partition`, but accepts an iterable
      of separator strings.  It can also partition more than once,
      and supports `reverse=True` which causes it to partition from the right
      (like `str.rpartition`).
    * Also added useful predefined lists of separators for use with all
      the `multi` functions: [`whitespace`](#whitespace) and [`newlines`](#newlines),
      with `ascii_` and `utf8_` versions of each, and `without_dos` variants of
      all three [`newlines`](#newlines) variants.
  * Added the
    [`Scheduler`](#schedulerregulatornone)
    and
    [`Heap`](#heapinone)
    classes.  [`Scheduler`](#schedulerregulatornone)
    is a replacement for Python's `sched.scheduler` class, with a modernized
    interface and a major upgrade in functionality.  [`Heap`](#heapinone)
    is an object-oriented interface to Python's `heapq` module, used by
    [`Scheduler`](#schedulerregulatornone).
    These are in their own modules, [`big.heap`](#bigheap) and [`big.scheduler`](#bigscheduler).
  * Added
    [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
    and all the `lines_` modifiers.  These are great for writing little text parsers.
    For more information, please see the deep-dive on
    [**`lines` and lines modifier functions.**](#lines-and-lines-modifier-functions)
  * Removed`stripped_lines` and `rstripped_lines` from the `text` module,
    as they're superceded by the far superior
    [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
    family.
  * Enhanced
    [`normalize_whitespace`](#normalize_whitespaces-separatorsNone-replacementNone).
    Added the `separators` and `replacement` parameters,
    and added support for `bytes` objects.
  * Added the `count` parameter to
    [`re_partition`](#re_partitiontext-pattern-count1--flags0)
    and
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0).
  </dd></dl>

  <dl><dd>
  *released 2022/09/12*
  * Added `stripped_lines` and `rstripped_lines` to the [`text`](#bigtext) module.
  * Added support for `len` to the [`TopologicalSorter`](#topologicalsortergraphnone) object.
  </dd></dl>

  <dl><dd>
  *released 2022/09/04*
  * Added
    [`gently_title`](#gently_titles-apostrophesnone-double_quotesnone)
    and
    [`normalize_whitespace`](#normalize_whitespaces-separatorsNone-replacementNone)
    to the [`text`](#bigtext) module.
  * Changed [`translate_filename_to_exfat`](#translate_filename_to_exfats)
    to handle translating `':'` in a special way.
    If the colon is followed by a space, then the colon is turned into `' -'`.
    This yields a more natural translation when colons are used in text, e.g.
    `'xXx: The Return Of Xander Cage'` is translated to `'xXx - The Return Of Xander Cage'`.
    If the colon is not followed by a space, turns the colon into `'-'`.
    This is good for tiresome modern gobbledygook like `'Re:code'`, which
    will now be translated to `'Re-code'`.
  </dd></dl>

  <dl><dd>
  *released 2022/06/12*
  * Initial release.
  </dd></dl>


pkgId: 31218ea18644804c69104b0874504debe2f7c4db4ac40c1ecbc0301f54487b26
name: python-big
arch: src
version: 0.12.4
epoch: 0
release: 1
summary: The big package is a grab-bag of cool code for use in your programs.
url: https://pypi.org/project/big/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-big-0.12.4-1.src.rpm
checksum_type: sha256
description:
  <ol><li id="re_strip_footnote"><p>
  And I should know--`multisplit` is implemented using `re.split`!
  </p></li></ol>
  </dd></dl>

  <dl><dd>

  Several functions in **big** take a `separators`
  argument, an iterable of separator strings.
  Examples of these functions include
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
  and
  [`multisplit`.](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
  Although you can use any iterable of strings
  you like, most often you'll be separating on some
  form of whitespace.  But what, exactly, *is*
  whitespace?  There's more to this topic than you
  might suspect.
  The good news is, you can almost certainly ignore all the
  complexity.  These days the only whitespace characters you're
  likely to encounter are spaces, tabs, newlines, and maybe
  carriage returns.  Python and **big** handle all those easily.
  With respect to **big** and these `separators` arguments,
  **big** provides four values designed for use as `separators`.
  All four of these are tuples containing whitespace characters:
  * When working with `str` objects, you'll want to use either
    [`big.whitespace`](#whitespace) or [`big.linebreaks`.](#linebreaks)
    `big.whitespace` contains all the whitespace characters,
    `big.linebreaks` contains just the line-breaking
    whitespace characters.
  * **big** also has equivalents for working with `bytes`
    objects: [`bytes_whitespace`](#bytes_whitespace)
    and [`bytes_linebreaks`,](#bytes_linebreaks)
    respectively.
  Apart from exceptionally rare occasions, these are all you'll ever need.
  And if that's all you need, you can stop reading this
  section now.
  But what about those exceptionally rare occasions?
  You'll be pleased to know **big** handles them too.
  The rest of this section is
  a deep dive into these rare occasions.

  Here's the list of all characters recognized by
  Python `str` objects as whitespace characters:
      
      
      '\t'    , #     9 - 0x0009 - tab
      '\n'    , #    10 - 0x000a - newline
      '\v'    , #    11 - 0x000b - vertical tab
      '\f'    , #    12 - 0x000c - form feed
      '\r'    , #    13 - 0x000d - carriage return
      '\x1c'  , #    28 - 0x001c - file separator
      '\x1d'  , #    29 - 0x001d - group separator
      '\x1e'  , #    30 - 0x001e - record separator
      '\x1f'  , #    31 - 0x001f - unit separator
      ' '     , #    32 - 0x0020 - space
      '\x85'  , #   133 - 0x0085 - next line
      '\xa0'  , #   160 - 0x00a0 - non-breaking space
      '\u1680', #  5760 - 0x1680 - ogham space mark
      '\u2000', #  8192 - 0x2000 - en quad
      '\u2001', #  8193 - 0x2001 - em quad
      '\u2002', #  8194 - 0x2002 - en space
      '\u2003', #  8195 - 0x2003 - em space
      '\u2004', #  8196 - 0x2004 - three-per-em space
      '\u2005', #  8197 - 0x2005 - four-per-em space
      '\u2006', #  8198 - 0x2006 - six-per-em space
      '\u2007', #  8199 - 0x2007 - figure space
      '\u2008', #  8200 - 0x2008 - punctuation space
      '\u2009', #  8201 - 0x2009 - thin space
      '\u200a', #  8202 - 0x200a - hair space
      '\u2028', #  8232 - 0x2028 - line separator
      '\u2029', #  8233 - 0x2029 - paragraph separator
      '\u202f', #  8239 - 0x202f - narrow no-break space
      '\u205f', #  8287 - 0x205f - medium mathematical space
      '\u3000', # 12288 - 0x3000 - ideographic space
  This list was derived by iterating over every character
  defined in Unicode, and testing to see if the `split()`
  method on a Python `str` object splits at that character.
  The first surprise: this *isn't* the same as the list of
  [all characters defined by Unicode as whitespace.](https://en.wikipedia.org/wiki/Whitespace_character#Unicode)
  It's *almost* the same list, except Python adds four extra
  characters: `'\x1c'`,  `'\x1d'`,  `'\x1e'`, and `'\x1f'`,
  which respectively are called "file separator", "group separator",
  "record separator", and "unit separator".
  I'll refer to these as "the four ASCII separator characters".
  These characters were defined as part of [the original ASCII
  standard,](https://en.wikipedia.org/wiki/ASCII) way back in 1963.
  As their names suggest, they were intended to be used as separator
  characters for data, the same way
  [Ctrl-Z was used to indicate end-of-file in the CPM and earliest
  FAT filesystems.](https://en.wikipedia.org/wiki/End-of-file#EOF_character)
  But the four ASCII separator characters were rarely used
  even back in the day.  Today they're practically unheard of.
  As a rule, printing these characters to the screen generally
  doesn't do anything--they don't move the cursor, and the
  screen doesn't change.
  So their behavior is a bit mysterious.  A lot of people (including
  early Python programmers it seems!) thought that meant they're
  whitespace.  This seems like an odd conclusion to me.  After all,
  all the other whitespace characters move the cursor, either right
  or down or both; these don't move the cursor at all.
  [The Unicode standard](https://www.unicode.org/Public/14.0.0/)
  is unambiguous: these characters are *not whitespace.*  And yet Python's
  "Unicode object" behaves as if they are.  So I'd say this is a bug;
  Python's Unicode object should implement what the Unicode standard says.
  > It seems that the C library used by GCC and clang on my workstation
  > agree.  I wrote a quick C program to print out what characters
  > are and aren't whitespace, according to the C function isspace().
  > It seems the C library agrees with Unicode: it doesn't consider
  > the four ASCII separator characters to be whitespace.
  >
  > Here's the program, in case you want to try it yourself.
  >
  >      #include <stdio.h>
  >      #include <ctype.h>
  >
  >      int main(int c, char *a[]) {
  >              int i;
  >              printf("\nisspace table.\nAdd the row and column numbers together (in hex).\n\n");
  >              printf("     | 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
  >              printf("-----+--------------------------------\n");
  >              for (i = 0 ; i < 256 ; i++) {
  >                      char *message = isspace(i) ? "Y" : "n";
  >                      if ((i % 16) == 0)
  >                              printf("0x%02x |", i);
  >                      printf(" %s", message);
  >                      if ((i % 16) == 15)
  >                              printf("\n");
  >              }
  >              return 0;
  >      }
  >
  > Here's its output on my workstation:
  >
  >     isspace table.
  >     Add the row and column numbers together (in hex).
  >
  >          | 0 1 2 3 4 5 6 7 8 9 a b c d e f
  >     -----+--------------------------------
  >     0x00 | n n n n n n n n n Y Y Y Y Y n n
  >     0x10 | n n n n n n n n n n n n n n n n
  >     0x20 | Y n n n n n n n n n n n n n n n
  >     0x30 | n n n n n n n n n n n n n n n n
  >     0x40 | n n n n n n n n n n n n n n n n
  >     0x50 | n n n n n n n n n n n n n n n n
  >     0x60 | n n n n n n n n n n n n n n n n
  >     0x70 | n n n n n n n n n n n n n n n n
  >     0x80 | n n n n n n n n n n n n n n n n
  >     0x90 | n n n n n n n n n n n n n n n n
  >     0xa0 | n n n n n n n n n n n n n n n n
  >     0xb0 | n n n n n n n n n n n n n n n n
  >     0xc0 | n n n n n n n n n n n n n n n n
  >     0xd0 | n n n n n n n n n n n n n n n n
  >     0xe0 | n n n n n n n n n n n n n n n n
  >     0xf0 | n n n n n n n n n n n n n n n n
  >
  > 0x1c through 0x1f are represented by the last four `n` characters on
  > the second line, the `0x10` line.  The fact that they're `n`s tells you
  > that this C standard library doesn't consider those characters to be
  > whitespace.
  Like many bugs, this one has lingered for a long time.  The behavior
  is present in Python 2, there's
  [a ten-year-old issue on the Python issue tracker about this,](https://github.com/python/cpython/issues/62436)
   and it's not making progress.
  The second surprise has to do with `bytes` objects.
  Of course, `bytes` objects represent binary data, and don't
  necessarily represent characters.  Even if they do, they don't
  have any encoding associated with them.  However, for
  convenience--and backwards-compatibility with Python 2--Python's
  `bytes` objects support several method calls that treat the data
  as if it were "ASCII-compatible".
  The surprise: These methods on Python `bytes` objects recognize
  a *different* set of whitespace characters.  Here's the list of
  all bytes recognized by Python `bytes` objects as whitespace:
      
      
      '\t'    , #  9 - 0x09 - tab
      '\n'    , # 10 - 0x0a - newline
      '\v'    , # 11 - 0x0b - vertical tab
      '\f'    , # 12 - 0x0c - form feed
      '\r'    , # 13 - 0x0d - carriage return
      ' '     , # 32 - 0x20 - space
  This list was derived by iterating over every possible
  byte value, and testing to see if the `split()` method
  on a Python `bytes` object splits at that byte.
  The good news is, this list is the same as ASCII's list,
  and it agrees with Unicode.
  In fact this list is quite familiar to C programmers;
  it's the same whitespace characters recognized by the
  standard C function
  [`isspace()` (in `ctypes.h`).](https://www.oreilly.com/library/view/c-in-a/0596006977/re129.html)
  Python has used this function to decide which characters
  are and aren't whitespace in 8-bit strings since its very
  beginning.
  Notice that this list *doesn't* contain the
  four ASCII separator characters.  That these two
  types in Python don't agree only enhances the mystery.

  The situation is slightly worse with line-breaking
  characters. Line-breaking characters are a subset of
  whitespace characters; they're whitespace characters that
  always move the cursor down.  And, as with whitespace
  generally, Python `str` objects don't agree with Unicode
  about what is and is not a line-breaking character,
  and Python `bytes` objects don't agree with either of those.
  Here's the list of all Unicode characters recognized by
  Python `str` objects as line-breaking characters:
      
      
      '\n'    , #   10 0x000a - newline
      '\v'    , #   11 0x000b - vertical tab
      '\f'    , #   12 0x000c - form feed
      '\r'    , #   13 0x000d - carriage return
      '\x1c'  , #   28 0x001c - file separator
      '\x1d'  , #   29 0x001d - group separator
      '\x1e'  , #   30 0x001e - record separator
      '\x85'  , #  133 0x0085 - next line
      '\u2028', # 8232 0x2028 - line separator
      '\u2029', # 8233 0x2029 - paragraph separator
  This list was derived by iterating over every character
  defined in Unicode, and testing to see if the `splitlines()`
  method on a Python `str` object splits at that character.
  Again, this is different from [the list of characters
  defined as line-breaking whitespace in Unicode.](https://en.wikipedia.org/wiki/Newline#Unicode)
  And again it's because Python defines some of the four ASCII separator
  characters as line-breaking characters.  In this case
  it's only the first three; Python doesn't consider
  the fourth, "unit separator", as a line-breaking character.
  (I don't know why Python draws this distinction...
  but then again, I don't know why it considers the
  first three to be line-breaking.  It's *all* a mystery to me.)
  Here's the list of all characters recognized by
  Python `bytes` objects as line-breaking characters:
      
      
      '\n'    , #  10 0x000a - newline
      '\r'    , #  13 0x000d - carriage return
  This list was derived by iterating over every possible
  byte, and testing to see if the `splitlines()`
  method on a Python `bytes` object splits at that byte.
  It's here we find our final unpleasant surprise:
  the methods on Python `bytes` objects *don't* consider
  `'\v'` (vertical tab)
  and
  `'\f'` (form feed)
  to be line-break characters.  I assert this is also a bug.
  These are well understood to be line-breaking characters;
  "vertical tab" is like a "tab", except it moves the cursor
  down instead of to the right.  And "form feed" moves the
  cursor to the top left of the next "page", which requires
  advancing at least one line.

  To be crystal clear: the odds that any of this will cause
  a problem for you are *extremely* low.  In order for it
  to make a difference:
  * you'd have to encounter text using one of these six characters
    where Python disagrees with Unicode and ASCII, and
  * you'd have to process the input based on some definition
    of whitespace, and
  * it would have to produce different results than you might
    have other wise expected, *and*
  * this difference in results would have to be important.
  It seems extremely unlikely that all of these will be true for you.
  In case this *does* affect you, **big** has
  a complete set of predefined whitespace tuples that will
  handle any of these situations.
  **big** defines a total of *ten* tuples, sorted into
  five categories.
  In every category there are two values: one that contains
  `whitespace`, the other contains `linebreaks`.  The
  `whitespace` tuple contains all the possible values of
  whitespace--characters that move the cursor either
  horizontally, or vertically, or both, but don't
  print anything visible to the screen.  The `linebreaks`
  tuple contains the subset of whitespace characters that
  move the cursor vertically.
  The most important two values start with `str_`:
  [`str_whitespace`](#str_whitespace)
  and
  [`str_linebreaks`.](#str_linebreaks)
  These contain all the whitespace characters
  recognized by the Python `str` object.
  Next are two values that start with `unicode_`:
  [`unicode_whitespace`](#unicode_whitespace)
  and
  [`unicode_linebreaks`.](#unicode_linebreaks)
  These contain all the whitespace characters
  defined in the Unicode standard.  They're the
  same as the `str_` tuples except we remove the
  four ASCII separator characters.
  Third, two values that start with `ascii_`:
  [`ascii_whitespace`](#ascii_whitespace)
  and
  [`ascii_linebreaks`.](#ascii_linebreaks)
  These contain all the whitespace characters
  defined in ASCII.  (Note that these contain
  `str` objects, not `bytes` objects.)  They're
  the same as the `unicode_` tuples, except we
  throw away all characters with a code point
  higher than 127.
  Fourth, two values that start with `bytes_`:
  [`bytes_whitespace`](#bytes_whitespace)
  and
  [`bytes_linebreaks`.](#bytes_linebreaks)
  These contain all the whitespace characters
  recognized by the Python `bytes` object.
  These tuples contain `bytes` objects, encoded
  using the `ascii` encoding.  The list of
  characters is distinct from the other sets
  of tuples, and was derived as described above.
  Finally we have the two tuples that lack a prefix:
  [`whitespace`](#whitespace)
  and
  [`linebreaks`.](#linebreaks)
  These are the tuples you should use most of the time,
  and several **big** functions use them as default values.
  These are simply copies of `str_whitespace` and
  `str_linebreaks` respectively.
  (**big** actually defines an *additional* ten tuples,
  as discussed in the very next section.)

  Historically, different platforms used different
  ASCII characters--or sequences of ASCII characters--to
  represent "go to the next line" in text files.  Here are the
  most popular conventions:
      \n    - UNIX, Amiga, macOS 10+
      \r    - macOS 9 and earlier, many 8-bit computers
      \r\n  - Windows, DOS
  (There are a couple more conventions, and a lot more history,
  in the [Wikipedia article on newlines.)](https://en.wikipedia.org/wiki/Newline#Representation)
  Handling these differing conventions was a stumbling block
  for a long time--both for computer programs, and in the
  daily lives of computer users.  Python went through several
  iterations on how to handle this, eventually settling on
  the ["universal newlines"](https://peps.python.org/pep-0278/)
  support added in Python 2.3.
  These days the world seems to be converging on the UNIX
  standard `'\n'`; Windows supports it, and it's the default
  on every other modern platform.
  So in practice you probably don't have end-of-line conversion
  problems, either.
  But just in case, **big** has one more trick.  All of
  the tuples defined in the previous section--from `whitespace`
  to `ascii_linebreaks`--also contain this string:
      '\r\n'
  (The two `bytes_` tuples contain the `bytes` equivalent,
  `b'\r\n`.)
  This addition means that, when you use one of these tuples
  with one of the **big** functions that take separators,
  it'll split on `\r\n` as if it was one character.  This
  means that **big** itself should automatically handle
  the DOS and Windows end-of-line character sequence, in
  case one happens to creep into your data.
  If you don't want this behavior, just add the suffix
  `_without_crlf` to the end of any of the ten tuples,
  e.g. `whitespace_without_crlf`, `bytes_linebreaks_without_crlf`.

  What if you need to split text by whitespace, or by lines,
  but that text is in `bytes` format with an unusual encoding?
  **big** makes that easy too.  If one of the builtin tuples
  won't work for you, you can can make your own tuple from scratch,
  or modify an existing tuple to meet your needs.
  For example, let's say you need to split a document by
  whitespace, and the document is encoded in [code page 850,
  aka "latin-1".](https://en.wikipedia.org/wiki/Code_page_850)
  Normally the easiest thing would be to decode it a `str` object
  using the `'latin-1'` text codec, then operate on it normally.
  But you might have reasons why you don't want to decode it--maybe
  the document is damaged and doesn't decode properly, and it's
  easier to work with the encoded bytes than to fix it.  If you
  want to process the text with a **big** function that accepts a
  `separator` argument, you could make your own custom tuple
  of "latin-1" whitespace characters.  "latin-1" has the same
  whitespace characters as ASCII, but adds one more, value 255,
  which is not line-breaking.  So it's easy to make the appropriate
  tuples yourself:
      latin_1_whitespace = big.bytes_whitespace + (b'\xff',)
      latin_1_linebreaks = big.bytes_linebreaks
  What if you want to process a `bytes` object containing
  UTF-8?  That's easy too.  Just convert one of the existing
  tuples containing `str` objects using
  [`big.encode_strings`.](#encode_stringso--encodingascii)
  For example, to split a UTF-8 encoded bytes object `b` using
  the Unicode line-breaking characters, you could call:
      multisplit(b, encode_strings(unicode_linebreaks, encoding='utf-8'))
  Note that this technique probably won't work correctly for most other
  multibyte encodings, for example [UTF-16.](https://en.wikipedia.org/wiki/UTF-16)
  For these encodings, you should decode to `str` before processing.
  Why?  It's because `multisplit` could find matches in multibyte
  sequences *straddling* characters.  Consider this example:
  ```Python
  >>> haystack = '\u0101\u0102'
  >>> needle = '\u0201'
  >>> needle in haystack
  False
  >>>
  >>> encoded_haystack = haystack.encode('utf-16-le')
  >>> encoded_needle = needle.encode('utf-16-le')
  >>> encoded_needle in encoded_haystack
  True
  ```
  The character `'\u0201'` doesn't appear in the original string,
  but the *encoded* version appears in the *encoded* string,
  as the *second* byte of the *first* character and the *first*
  byte of the *second* character:
  ```Python
  >>> encoded_haystack
  b'\x01\x01\x02\x01'
  >>> encoded_needle
  b'\x01\x02'
  ```
  </dd></dl>

  <dl><dd>
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
  is a function that makes it easy to write well-behaved, feature-rich text parsers.
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) itself
  iterates over a string, returning an iterator that yields individual lines
  split from that string.  The iterator yields a 2-tuple:
  ```
  (LinesInfo, line)
  ```
  The `LinesInfo` object provides the line number and starting column number
  for each line.  This makes it easy for your parser to provide
  line and column information for error messages.
  This iterator is designed to be modified by "lines modifier"
  functions.  These are functions that consume a lines
  iterator and re-yield the values, possibly modifying or
  discarding them along the way.  For example, passing
  a [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) iterator into `lines_filter_empty_lines` results
  in an iterator that skips over the empty lines.
  All the lines modifier functions that ship with **big**
  start with the string `lines_`.
  Most lines modifier function names belong to a category,
  encoded as the second word in the function name
  (immediately after `lines_`).  Some examples:
  * `lines_filter_` functions conditionally remove
    lines from the output.  For example, `lines_filter_empty_lines`
    will only yield a line if it isn't empty.
  * `lines_strip_` functions may remove one or
    more substrings from the line.  For example,
    [`lines_strip_indent`](#lines_strip_indentli)
    strips the leading whitespace from a line before yielding
    it.  (Whenever a lines modifier removes leading text from a line,
    it will add a `leading` field to the accompanying
    [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
    object containing the removed substring, and will also update the
    `column_number` of the line to reflect the new starting column.)
  * `lines_convert_` functions means this lines modifier may change one
    or more substrings in the line.  For example,
    `lines_convert_tabs_to_spaces` changes tab characters
    to space characters in any lines it processes.
  (**big** isn't strictly consistent about these category names though.
  For example,
  [`lines_containing`](#lines_containingli-s--invertfalse)
  and
  [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0)
  are obviously "filter" modifiers, but their names
  don't start with `lines_filter_`.)
  All lines modifier functions are composable with each
  other; you can "stack" them together simply by passing
  the output of one into the input of another.  For example,
  ```Python
       with open("textfile.txt", "rt") as f:
           for info, line in big.lines_filter_empty_lines(
       	     big.lines_rstrip(lines(f.read()))):
  ```
  will iterate over the lines of `textfile.txt`, skipping
  over all empty lines and lines that consist only of
  whitespace.
  When you stack line modifiers in this way, note that the
  *outer* modifiers happen *later*.  In the above example,
  each line is first "r-stripped", and then discarded
  if it's empty.  If you stacked the line modifiers in
  the opposite order:
  ```Python
       with open("textfile.txt", "rt") as f:
           for info, line in big.lines_rstrip(
       	     big.lines_filter_empty_lines(lines(f.read()))):
  ```
  then it'd filter out empty lines first, and *then*
  "r-strip" the lines.  So lines in the input that contained
  only whitespace would still get yielded as empty lines,
  which is probably not what you want.  Ordering is important!
  It's probably clearer to constructed nested lines modifiers
  this way:
  ```Python
       with open("textfile.txt", "rt") as f:
           li = lines(f.read())
           li = big.lines_filter_empty_lines(li)
           li = big.lines_rstrip(li)
           for info, line in li:
  ```
  This is much easier to read, particularly when one or
  more lines modifiers take additional arguments.
  Of course, you can write your own lines modifier functions.
  Simply accept a lines iterator as an argument, iterate over
  it, and yield each line info and line--modifying them
  (or not yielding them!) as you see fit.  You could
  even write your own lines iterator, a replacement for
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs),
  if you need functionality
  [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs) doesn't provide.
  Note that if you write your own lines modifier function,
  and it removes text from the beginning the line, you must
  update `column_number` in the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  object manually--it
  doesn't happen automatically.  The easiest way to handle this
  is also the best way: whenever clipping text from the beginning
  or end of the line, use the
  [`clip_leading`](#lineinfoclip_leadingline-s)
  and
  [`clip_trailing`](#lineinfoclip_trailingline-s)
  methods on the `LineInfo` object.
  Speaking of best practices for lines modifier functions,
  it's also best practice to *modify* the *existing*
  `LineInfo` object that was yielded to you, rather than
  throwing it away, creating a new one, and yielding that
  instead.  Previous lines modifier iterators may have added
  fields to the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  that you'd to preserve.

  Generally speaking,
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects obey an invariant.
  For any `(info, line)` pair yielded by `lines` or a lines
  modifier:
      info.leading + line + info.trailing + info.end == info.line
  That is, you can recreate the original line by concatenating
  the "leading" string, the modified line, the "trailing" string,
  and the "end" string.
  Of course, this won't be true if you use lines modifiers that
  replace characters in the line.  For example, `lines_convert_tabs_to_spaces`
  replaces tab characters with one or more space characters.
  If the original line contains tabs, obviously the above invariant
  will no longer hold true.
  </dd></dl>

  <dl><dd>
  **big** contains three functions used to reflow and format text
  in a pleasing manner.  In the order you should use them, they are
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue),
  [`wrap_words(),`](#wrap_wordswords-margin79--two_spacestrue),
  and optionally
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0).
  This trio of functions gives you the following word-wrap superpowers:
  * Paragraphs of text representing embedded "code" don't get word-wrapped.
    Instead, their formatting is preserved.
  * Multiple texts can be merged together into multiple columns.

  The **big** word wrapping functions also distinguish between
  "text" and "code".  The main distinction is, "text" lines can
  get word-wrapped, but "code" lines shouldn't.  **big** considers
  any line starting with enough whitespace to be a "code" line;
  by default, this is four spaces.  Any non-blank line that
  starting with four spaces is a "code" line, and any non-blank
  line that starts with less than four spaces is a "text" line.
  In "text" mode:
  * words are separated by whitespace,
  * initial whitespace on the line is discarded,
  * the amount of whitespace between words is irrelevant,
  * individual newline characters are ignored, and
  * more than two newline characters are converted into exactly
    two newlines (aka a "paragraph break").
  In "code" mode:
  * all whitespace is preserved, except for trailing whitespace on a line, and
  * all newline characters are preserved.
  Also, whenever
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  switches between
  "text" and "code" mode, it emits a paragraph break.

  A *split text array* is an intermediary data structure
  used by **big.text** functions to represent text.
  It's literally just an array of strings, where the strings
  represent individual word-wrappable substrings.
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  returns a *split text array,* and
  [`wrap_words()`](#wrap_wordswords-margin79--two_spacestrue)
  consumes a *split text array.*
  You'll see four kinds of strings in a *split text array:*
  * Individual words, ready to be word-wrapped.
  * Entire lines of "code", preserving their formatting.
  * Line breaks, represented by a single newline: `'\n'`.
  * Paragraph breaks, represented by two newlines: `'\n\n'`.

  This might be clearer with an example or two.  The following text:
  ```
  hello there!
  this is text.
  this is a second paragraph!
  ```
  would be represented in a Python string as:
  ```Python
  "hello there!\nthis is text.\n\n\nthis is a second paragraph!"
  ```
  Note the three newlines between the second and third lines.
  If you then passed this string in to
  [`split_text_with_code`,](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  it'd return this *split text array:*
  ```Python
  [ 'hello', 'there!', 'this', 'is', 'text.', '\n\n',
    'this', 'is', 'a', 'second', 'paragraph!']
  ```
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  merged the first two lines together into
  a single paragraph, and collapsed the three newlines separating
  the two paragraphs into a "paragraph break" marker
  (two newlines in one string).
  Now let's add an example of text with some "code".  This text:
  ```
  What are the first four squared numbers?
      for i in range(1, 5):
          print(i**2)
  Python is just that easy!
  ```
  would be represented in a Python string as (broken up into multiple strings for clarity):
  ```Python
  "What are the first four squared numbers?\n\n"
  +
  "    for i in range(1, 5):\n\n\n"
  +
  "        print(i**2)\n\nPython is just that easy!"
  ```
  [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
  considers the two lines with initial whitespace as "code" lines,
  and so the text is split into the following *split text array:*
  ```Python
  ['What', 'are', 'the', 'first', 'four', 'squared', 'numbers?', '\n\n',
    '    for i in range(1, 5):', '\n', '\n', '\n', '        print(i**2)', '\n\n',
    'Python', 'is', 'just', 'that', 'easy!']
  ```
  Here we have a "text" paragraph, followed by a "code" paragraph, followed
  by a second "text" paragraph.  The "code" paragraph preserves the internal
  newlines, though they are represented as individual "line break" markers
  (strings containing a single newline).  Every paragraph is separated by
  a "paragraph marker".
  Here's a simple algorithm for joining a *split text array* back into a
  single string:
  ```Python
  prev = None
  a = []
  for word in split_text_array:
      if not (prev and prev.isspace() and word.isspace()):
          a.append(' ')
      a.append(word)
  text = "".join(a)
  ```
  Of course, this algorithm is too simple to do word wrapping.
  Nor does it handle adding two spaces after sentence-ending
  punctuation.  In practice, you shouldn't do this by hand;
  you should use
  [`wrap_words`](#wrap_wordswords-margin79--two_spacestrue).

  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  merges multiple strings into columns on the same line.
  For example, it could merge these three Python strings:
  ```Python
  [
  "Here's the first\ncolumn of text.",
  "More text over here!\nIt's the second\ncolumn!  How\nexciting!",
  "And here's a\nthird column.",
  ]
  ```
  into the following text:
  ```
  Here's the first    More text over here!   And here's a
  column of text.     It's the second        third column.
                      column!  How
                      exciting!
  ```
  (Note that
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  doesn't do its own word-wrapping;
  instead, it's designed to consume the output of
  [`wrap_words`](#wrap_wordswords-margin79--two_spacestrue).)
  Each column is passed in to
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  as a "column tuple":
  ```Python
  (s, min_width, max_width)
  ```
  `s` is the string,
  `min_width` is the minimum width of the column, and
  `max_width` is the minimum width of the column.
  As you saw above, `s` can contain newline characters,
  and
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  obeys those when formatting each column.
  For each column,
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  measures the longest
  line of each column.  The width of the column is determined
  as follows:
  - If the longest line is less than `min_width` characters long,
    the column will be `min_width` characters wide.
  - If the longest line is less than or equal to `min_width`
    characters long, and less than or equal to `max_width`
    characters long, the column will be as wide as the longest line.
  - If the longest line is greater than `max_width` characters long,
    the column will be `max_width` characters wide, and lines that
    are longer than `max_width` characters will "overflow".

  What is "overflow"?  It's a condition
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  may encounter when the text in a column is wider than that
  column's `max_width`.
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  needs to consider both "overflow lines",
  lines that are longer than `max_width`, and "overflow columns",
  columns that contain one or more overflow lines.
  What does
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  do when it encounters overflow?
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  supports three "strategies" to deal with this condition, and you can specify
  which one you want using its `overflow_strategy` parameter.  The three
  strategies are:
  - `OverflowStrategy.RAISE`: Raise an `OverflowError` exception.  The default.
  - `OverflowStrategy.INTRUDE_ALL`: Intrude into all subsequent columns on
  all lines where the overflowed column is wider than its `max_width`.
  The subsequent columns "make space" for the overflow text by not adding
  text on those overflowed lines; this is called "pausing" their output.
  - `OverflowStrategy.DELAY_ALL`:  Delay all columns after the overflowed
  column, not beginning any until after the last overflowed line
  in the overflowed column.  This is like the `INTRUDE_ALL` strategy,
  except that the columns "make space" by pausing their output until
  the last overflowed line.
  When `overflow_strategy` is `INTRUDE_ALL` or `DELAY_ALL`, and
  either `overflow_before` or `overflow_after` is nonzero, these
  specify the number of extra lines before or after
  the overflowed lines in a column where the subsequent columns
  "pause".
  </dd></dl>

  <dl><dd>

  **big**'s [`TopologicalSorter`](#topologicalsortergraphnone)
  is a drop-in replacement for
  [`graphlib.TopologicalSorter`](https://docs.python.org/3/library/graphlib.html#graphlib.TopologicalSorter)
  in the Python standard library (new in 3.9).
  However, the version in **big** has been greatly upgraded:
  - `prepare` is now optional, though it still performs a cycle check.
  - You can add nodes and edges to a graph at any time, even while
    iterating over the graph.  Adding nodes and edges always succeeds.
  - You can remove nodes from graph `g` with the new method `g.remove(node)`.
    Again, you can do this at any time, even while iterating over the graph.
    Removing a node from the graph always succeeds, assuming the node is in the graph.
  - The functionality for iterating over a graph now lives in its own object called
    a *view*.  View objects implement the `get_ready`, `done`, and `__bool__`
    methods.  There's a default view built in to the graph object;
    the `get_ready`, `done`, and `__bool__` methods on a graph just call
    into the graph's default view.  You can create a new view at any time
    by calling the new `view` method.
  Note that if you're using a view to iterate over the graph, and you modify the graph,
  and the view now represents a state that isn't *coherent* with the graph,
  attempting to use that view raises a `RuntimeError`.  (I'll define what I mean
  by view "coherence" in the next subsection.)
  This implementation also fixes some minor warts with the existing API:
  - In Python's implementation, `static_order` and `get_ready`/`done` are mutually exclusive.  If you ever call
    `get_ready` on a graph,  you can never call `static_order`, and vice-versa.  The implementaiton in **big**
    doesn't have this restriction, because its implementation of `static_order` creates and uses a new view object
    every time it's called.
  - In Python's implementation, you can only iterate over the graph once, or call `static_order` once.
    The implementation in **big** solves this in several ways: it allows you to create as many views as you
    want, and you can call the new `reset` method on a view to reset it to its initial state.

  So what does it mean for a view to no longer be coherent with the graph?
  Consider the following code:
  ```Python
  g = big.TopologicalSorter()
  g.add('B', 'A')
  g.add('C', 'A')
  g.add('D', 'B', 'C')
  g.add('B', 'A')
  v = g.view()
  g.ready() # returns ('A',)
  g.add('A', 'Q')
  ```
  First this creates a graph `g` with a classic "diamond"
  dependency pattern.  Then it creates a new view `v`, and gets
  the currently "ready" nodes, which consists just of the node
  `'A'`.  Finally it adds a new dependency: `'A'` depends on `'Q'`.
  At this moment, view `v` is no longer _coherent._  `'A'` has been
  marked as "ready", but `'Q'` has not.  And yet `'A'` depends on `'Q'`.
  All those statements can't be true at the same time!
  So view `v` is no longer _coherent,_  and any attempt to interact
  with `v` raises an exception.
  To state it more precisely: if view `v` is a view on graph `g`,
  and you call `g.add('Z', 'Y')`,
  and neither of these statements is true in view `v`:
  - `'Y'` has been marked as `done`.
  - `'Z'` has not yet been yielded by `get_ready`.
  then `v` is no longer "coherent".
  (If `'Y'` has been marked as `done`, then it's okay to make `'Z'` dependent on
  `'Y'` regardless of what state `'Z'` is in.  Likewise, if `'Z'` hasn't been yielded
  by `get_ready` yet, then it's okay to make `'Z'` dependent on `'Y'` regardless
  of what state `'Y'` is in.)
  Note that you can restore a view to coherence.  In this case,
  removing either `Y` or `Z` from `g` would resolve the incoherence
  between `v` and `g`, and `v` would start working again.
  Also note that you can have multiple views, in various states of iteration,
  and by modifying the graph you may cause some to become incoherent but not
  others.  Views are completely independent from each other.
  </dd></dl>

  <dl><dd>

  One minor complaint I have about Python regards inner classes.
  An "inner class" is a class defined inside another class.  And,
  well, inner classes seem kind of half-baked.  Unlike functions,
  inner classes don't get bound to the object.
  Consider this Python code:
  ```Python
  class Outer(object):
      def method(self):
          pass
      class Inner(object):
          def __init__(self):
              pass
  o = Outer()
  o.method()
  i = o.Inner()
  ```
  When `o.method` is called, Python automatically passes in the `o` object as the first parameter
  (generally called `self`).  In object-oriented lingo, `o` is *bound* to `method`, and indeed
  Python calls this object a *bound method*:
  ```
      >>> o.method
      <bound method Outer.method of <__main__.Outer object at 0x########>>
  ```
  But that doesn't happen when `o.Inner` is called.  (It *does* pass in
  a `self`, but in this case it's the newly-created `Inner` object.)
  There's just no built-in way for the `o.Inner` object being constructed
  to *automatically* get a reference to `o`.  If you need one, you must
  explicitly pass one in, like so:
  ```Python
  class Outer(object):
      def method(self):
          pass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
  o = Outer()
  o.method()
  i = o.Inner(o)
  ```
  This seems redundant.  You don't have to pass in `o` explicitly to method calls,
  why should you have to pass it in explicitly to inner classes?
  Well--now you don't have to!
  You just decorate the inner class with `@big.BoundInnerClass`,
  and `BoundInnerClass` takes care of the rest!

  Let's modify the above example to use our [`BoundInnerClass`](#boundinnerclasscls)
  decorator:
  ```Python
  from big import BoundInnerClass
  class Outer(object):
      def method(self):
          pass
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
  o = Outer()
  o.method()
  i = o.Inner()
  ```
  Notice that `Inner.__init__` now requires an `outer` parameter,
  even though you didn't pass in any arguments to `o.Inner`.
  When it's called, `o` is magically passed in to `outer`!
  Thanks, [`BoundInnerClass`](#boundinnerclasscls)!  You've saved the day!
  Decorating an inner class like this always adds a second positional
  parameter, after `self`.  And, like `self`, you don't have
  to use the name `outer`, you can use any name you like.
  (Although it's probably a good idea, for consistency's sakes.)

  Bound inner classes get slightly complicated when mixed with inheritance.
  It's not all that difficult, you merely need to obey the following rules:
  1. *A bound inner class can inherit normally from any unbound class.*
  2. *To subclass from a bound inner class while still inside the outer
  class scope, or when referencing the inner class from the outer class
  (as opposed to an instance of the outer class), you must actually
  subclass or reference `classname.cls`.*  This is because inside the
  outer class, the "class" you see is actually an instance of a
  [`BoundInnerClass`](#boundinnerclasscls) object.
  3. *All classes that inherit from a bound inner class must always call the
  superclass's `__init__`. You don't need to pass in the `outer` parameter;
  it'll be automatically passed in to the superclass's `__init__` as before.*
  4. *An inner class that inherits from a bound inner class, and which also
  wants to be bound to the outer object, should be decorated with
  [`BoundInnerClass`](#boundinnerclasscls).*
  5. *An inner class that inherits from a bound inner class, but doesn't
  want to be bound to the outer object, should be decorated with
  [`UnboundInnerClass`](#unboundinnerclasscls).*
  Restating the last two rules: every class that descends from any
  [`BoundInnerClass`](#boundinnerclasscls)
  should be decorated with either
  [`BoundInnerClass`](#boundinnerclasscls)
  or
  [`UnboundInnerClass`](#unboundinnerclasscls).
  Which one you use depends on what behavior you want--whether or
  not you want your inner subclass to automatically get the `outer`
  instance passed in to its `__init__`.
  Here's a simple example using inheritance with bound inner classes:
  ```Python
  from big import BoundInnerClass, UnboundInnerClass
  class Outer(object):
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
      @UnboundInnerClass
      class ChildOfInner(Inner.cls):
          def __init__(self):
              super().__init__()
  o = Outer()
  i = o.ChildOfInner()
  ```
  We followed the rules:
  * `Inner` inherits from object; since object isn't a bound inner class,
    there are no special rules about inheritance `Inner` needs to obey.
  * `ChildOfInner` inherits from `Inner.cls`, not `Inner`.
  * Since `ChildOfInner` inherits from a
    [`BoundInnerClass`](#boundinnerclasscls),
    it must be
    decorated with either [`BoundInnerClass`](#boundinnerclasscls)
    or [`UnboundInnerClass`](#unboundinnerclasscls).
    It doesn't want the outer object passed in, so it's decorated
    with [`UnboundInnerClass`](#unboundinnerclasscls).
  * `ChildOfInner.__init__` calls `super().__init__`.
  Note that, because `ChildOfInner` is decorated with
  [`UnboundInnerClass`](#unboundinnerclasscls),
  it doesn't take an `outer` parameter.  Nor does it pass in an `outer`
  argument when it calls `super().__init__`.  But when the constructor for
  `Inner` is called, the correct `outer` parameter is passed in--like magic!
  Thanks again, [`BoundInnerClass`](#boundinnerclasscls)!
  If you wanted `ChildOfInner` to also get the outer argument passed in to
  its `__init__`, just decorate it with [`BoundInnerClass`](#boundinnerclasscls)
  instead of
  [`UnboundInnerClass`](#unboundinnerclasscls),
  like so:
  ```Python
  from big import BoundInnerClass
  class Outer(object):
      @BoundInnerClass
      class Inner(object):
          def __init__(self, outer):
              self.outer = outer
      @BoundInnerClass
      class ChildOfInner(Inner.cls):
          def __init__(self, outer):
              super().__init__()
              assert self.outer == outer
  o = Outer()
  i = o.ChildOfInner()
  ```
  Again, `ChildOfInner.__init__` doesn't need to explicitly
  pass in `outer` when calling `super.__init__`.
  You can see more complex examples of using inheritance with
  [`BoundInnerClass`](#boundinnerclasscls)
  (and [`UnboundInnerClass`](#unboundinnerclasscls))
  in the **big** test suite.

  * If you refer to a bound inner class directly from the outer *class,*
    rather than using the outer *instance,* you get the original class.
    This ensures that references to `Outer.Inner` are consistent; this
    class is also a base class of all the bound inner classes. Additionally,
    if you attempt to construct an instance of an unbound `Outer.Inner`
    class without referencing it via an instance, you must pass in the
    outer parameter by hand--just like you'd have to pass in the `self`
    parameter by hand when calling a method on the *class itself* rather
    than on an *instance* of the class.
  * If you refer to a bound inner class from an outer instance,
    you get a subclass of the original class.
  * Bound classes are cached in the outer object, which both provides
    a small speedup and ensures that `isinstance` relationships are
    consistent.
  * You must not rename inner classes decorated with either
    [`BoundInnerClass`](#boundinnerclasscls)
    or [`UnboundInnerClass`](#unboundinnerclasscls)!
    The implementation of
    [`BoundInnerClass`](#boundinnerclasscls)
    looks up
    the bound inner class in the outer object by name in several places.
    Adding aliases to bound inner classes is harmless, but the original
    attribute name must always work.
  * Bound inner classes from different objects are different classes.
    This is symmetric with bound methods; if you have two objects
    `a` and `b` that are instances of the same class,
    `a.BoundInnerClass != b.BoundInnerClass`, just as `a.method != b.method`.
  * The binding only goes one level deep; if you had an inner class `C`
    inside another inner class `B` inside a class `A`, the constructor
    for `C` would be called with the `B` object, not the `A` object.
  * Similarly, if you have a bound inner class `B` inside a class `A`,
    and another bound inner class `D` inside a class `C`, and `D`
    inherits from `B`, the constructor for `D` will be called with
    the `B` object but not the `A` object. When `D` calls `super().__init__`
    it'll have to fill in the `outer` parameter by hand.
  * There's a race condition in the implementation: if you access a
    bound inner class through an outer instance from two separate threads,
    and the bound inner class was not previously cached, the two threads
    may get different (but equivalent) bound inner class objects, and only
    one of those instances will get cached on the outer object.  This could
    lead to confusion and possibly cause bugs. For example, you could have
    two objects that would be considered equal if they were instances of
    the same bound inner class, but would not be considered equal if
    instantiated by different instances of that same bound inner class.
    There's an easy workaround for this problem: access the bound inner
    class from the `__init__` of the outer class, which should allow
    the code to cache the bound inner class instance before a second
    thread could ever get a reference to the outer object.
  </dd></dl>


  *2024/11/15*
  <dl><dd>
  * New function in the `text` module: `format_map`.
    This works like Python's `str.format_map` method,
    except it allows nested curly-braces.  Example:
    `big.format_map("The {extension} file is {{extension} size} bytes.", {'extension': 'mp3', 'mp3 size': 8555})`
  * New method: `Version.format` is like `strftime` but for `Version` objects.
    You pass in a format string with `Version` attributes in curly braces
    and it formats the string with values from that `Version` object.
  * The `Version` constructor now accepts a `packaging.Version` object
    as an initializer.  Embrace and extend!
  * `lines` now takes two new arguments:
      * `clip_linebreaks`, default is true.
        If true, it clips the linebreaks off the lines before yielding them,
        otherwise it doesn't.  (Either way, the linebreaks are still stored
        in `info.end`.)
      * `source`, default is an empty string.
        `source` should represent the source of the line in a
        meaninful way to the user.  It's stored in the `LinesInfo`
        objects yielded by `lines`, and should be incorporated into
        error messages.
  * `LineInfo.clip_leading` and `LineInfo.clip_trailing` now automatically
    detect if you've clipped the entire line, and if so move all clipped
    text to `info.trailing` (and adjust the `column_number` accordingly).
  * `LineInfo.clip_leading` and `LineInfo.clip_trailing`: Minor performance
    upgrade. Previously, if the user passed in the string to clip, the
    two functions would throw it away then recreate it.  Now they just use
    the passed-in string.
  * Changed the word "newline" to "linebreak" everywhere.  They mean the
    same thing, but the Unicode standard consistently uses the word
    "linebreak"; I assume the boffins on the committee thought about this
    a lot and argued and finally settled on this word for good
    (if unpublished?) reasons.
  * Add explicit support (and CI coverage & testing) for Python 3.13.
    (big didn't need any changes, it was already 100% compatible with 3.13.)
  <p><font size=1>p.s. 56</font></p>
  </dd></dl>

  *2024/09/17*
  <dl><dd>
  Optimized
  [`split_delimiters`](#split_delimiterss-delimiters--state).
  The new version uses a much more efficient internal representation
  of how to react to the various delimiters when processing the text.
  Perfunctory `timeit` experiments suggest this new `split_delimiters`
  is maybe 5-6% faster than it was in 12.2.
  Minor breaking change: `split_delimiters` now consistently
  raises `SyntaxError` for mismatched delimiters.  (Previously it
  would sometimes raise `ValueError`.)
  </dd></dl>

  *2024/09/11*
  <dl><dd>
  * A minor semantic change to [`lines_strip_indent`](#lines_strip_indentli):
    when it encounters a whitespace-only line, it clips the line to *trailing*
    in the `LineInfo` object.  It used to clip such lines to *leading*.  But this
    changed `LineInfo.column_number` in a nonsensical way.
    This behavior is policy going forward: if a lines modifer function ever clips
    the entire line, it must clip it to *trailing* rather than *leading*.  It
    shouldn't matter one way or another, as whitespace-only lines arguably
    shouldn't have any explicit semantics.  But it makes intuitive sense to me
    that their empty line should be at column number 1, rather than 9 or 13
    or whatnot.  (Especially considering that with `lines_strip_indent` their
    indent value is synthetic anyway, inferred by looking ahead.)
  * Major cleanup to the lines modifier test suites.
  </dd></dl>

  *2024/09/07*
  <dl><dd>
  In fine **big** tradition, here's an update published
  immediately after a big release.
  Surprisingly, even though this is only a small update,
  it still adds *two* new packages to **big**: *metadata* and *version*.
  There's sadly one breaking change.

  <dl><dd>
  New package.
  A package containing metadata about **big** itself.
  Currently only contains one thing: *version*.
  </dd></dl>

  <dl><dd>
  New package.  A package for working with version information.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  The default value for `quotes` has changed.  Now it's
  what it should always have been: empty.  No quote marks
  are defined by default, which means the default behavior of
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  is now to simply truncate the line
  at the leftmost comment marker.
  Processing quote marks by default was *always* too opinionated
  for this function.  Consider: having `'` active as a quote
  marker meant that single-quotes need to be balanced,
  >    which means you can't process a line like this that only has one.
  Wish I'd figured this out before the release yesterday!  Hopefully
  this will only cause smiles, and no teeth-gnashing.
  </dd></dl>

  <dl><dd>
  New value.  A
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  object representing the current version of **big**.
  </dd></dl>

  <dl><dd>
  New class.
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  represents a version number.  You can
  construct them from [PEP 440](https://peps.python.org/pep-0440/)-compliant
  version strings, or specify them using keyword-only parameters.
  [`Version`](#versionsnone--epochnone-releasenone-release_levelnone-serialnone-postnone-devnone-localnone)
  objects are immutable, ordered, and hashable.
  </dd></dl>

  <dl><dd>
  *2024/09/06*
  Lots of changes this time!  Most of 'em are in the `big.text`
  module, particularly the `lines` and _lines modifier_
  functions.  But plenty of other modules got in on the fun too.
  **big** even has a new module: `deprecated`.  Deprecated
  functions and classes get moved into this module.  Note that
  the contents of `deprecated` are not automatically imported
  into `big.all`.
  The following functions and classes have breaking changes:
  <dl><dd>
  [`Delimiter`](#delimiterclose--escape-multilinetrue-quotingfalse)
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
  </dd></dl>
  These functions have been renamed:
  <dl><dd>
  `lines_filter_comment_lines` is now [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers)
  `lines_strip_comments` is now [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  `parse_delimiters` is now [`split_delimiters`](#split_delimiterss-delimiters--state)
  </dd></dl>
  **big** has five new functions:
  <dl><dd>
  [`combine_splits`](#combine_splitss-split_arrays)
  [`encode_strings`](#encode_stringso--encodingascii)
  [`LineInfo.clip_leading`](#lineinfoclip_leadingline-s)
  [`LineInfo.clip_trailing`](#lineinfoclip_trailingline-s)
  [`split_title_case`](split_title_cases--split_allcapstrue)
  </dd></dl>
  </dd></dl>
  Finally, here's an in-depth description of all changes
  in **big** 0.12, sorted by API name.

  <dl><dd>
  Extremely minor change!  Python's `bytes` and `str` objects
  don't agree on which ASCII characters represent line breaks.
  The `str` object obeys the Unicode standard, which means
  there are four:
      \n \v \f \r
  For some reason, Python's `bytes` object only supports two:
      \n \r
  I have no idea why this is.  We might fix it.  And if we do,
  **big** is ready.  It now calculates
  [`bytes_linebreaks`](#bytes_linebreaks)
  and
  [`bytes_linebreaks_without_crlf`](#bytes_linebreaks_without_crlf)
  on the fly to agree with Python.
  If either (or both) work as newline characters for the `splitlines`
  method on a `bytes` object, they'll automatically be inserted
  into these iterables of bytes linebreaks.
  </dd></dl>

  <dl><dd>
  New function. If you split a string two different ways,
  producing two arrays that sum to the original string,
  `combine_splits` will merge those splits together, producing
  a new array that splits in every place any of the two split
  arrays had a split.
  Example:
      >>> big.combine_splits("abcdefg", ['a', 'bcdef', 'g'], ['abc', 'd', 'efg'])
      ['a, 'bc', 'd', 'ef', 'g']
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  `Delimiter` is a simple data class, representing information about
  delimiters to
  [`split_delimiters`](#split_delimiterss-delimiters--state)
   (previously `parse_delimiters`).
  `split_delimiters` has changed, and some of those changes are
  reflected in the `Delimiter` object; also, some changes to `Delimiter`
  are simply better API choices.
  The old `Delimiter` object is deprecated but still available,
  as `big.deprecated.Delimiter`.  It should only be used with
  `big.deprecated.parse_delimiters`, which is also deprecated.
  `big.deprecated.Delimiter` will be removed when
  `big.deprecated.parse_delimiters` is removed, which will be
  no sooner than September 2025.
  Changes:
  * The first argument to the old `Delimiter` object was `open`,
    and was stored as the `open` attribute.  These have both been
    completely removed.  Now, the "open delimiter" is specified
    as a key in a dictionary of delimiters, mapping open delimiters
    to `Delimiter` objects.
  * The old `Delimiter` object had a boolean `backslash` attribute;
    if it was True, that delimiter allows escaping using a backslash.
    Now `Delimiter` has an `escape` parameter and attribute,
    specifying the escape string you want to use inside that
    set of delimiters.
  * `Delimiter` also now has two new attributes, `quoting` and
    `multiline`.  These default to `False` and `True` respectively;
    you can specify values for these with keyword-only arguments
    to the constructor.
  * The new `Delimiter` object is read-only after construction,
    and is hashable.
  </dd></dl>

  <dl><dd>
  Slightly liberalized the types it accepts.  It previously
  required `o` to be a collection; now `o` can be a `bytes`
  or `str` object.  Also, it now explicitly supports `set`.
  </dd></dl>

  <dl><dd>
  Minor behavior change.  If the `o` you pass in is a `float`,
  or can be converted to `float` (but couldn't be converted directly
  to an `int`), `get_int_or_float` will experimentally convert that
  `float` to an `int`.  If the resulting `int` compares equal to that
  `float`, it'll return the `int`, otherwise it'll return the `float`.
  For example, `get_int_or_float("13.5")` still returns `13.5`
  (a `float`), but `get_int_or_float("13.0")` now returns `13`
  (an `int`).  (Previously, `get_int_or_float("13.0")` would
  have returned `13.0`.)
  This better represents the stated aesthetic of the function--it
  prefers ints to floats.  And since the int is exactly equal to
  the float, I assert this is completely backwards compatible.
  </dd></dl>

  <dl><dd>
  Minor updates to the documentation and to the text of some exceptions.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  Breaking change: the
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  constructor has a
  new `lines` positional parameter, added *in front of*
  the existing positional parameters.  This new first argument
  should be the  `lines` iterator that yielded this
  `LineInfo` object.  It's stored in the `lines` attribute.
  (Why this change?  The `lines` object contains information
  needed by the lines modifiers, for example `tab_width`.)
  Minor optimization:
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects previously had many
  optional fields, which might or might not be added
  dynamically.  Now all fields are pre-added.  (This makes
  the CPython 3.13 runtime happier; it really wants you to
  set *all* your class's attributes in its `__init__`.)
  Minor breaking change: the original string stored in the
  `line` attribute now includes the linebreak character, if any.
  This means concatenating all the `info.line` strings
  will reconstruct the original `s` passed in to `lines`.
  New feature: while some methods used to update the `leading`
  attribute when they clipped leading text from the line,
  the "lines modifiers" are now very consistent about updating
  `leading`, and the new symmetrical attribute `trailing`.
  New feature:
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  now has an `end` attribute,
  which contains the end-of-line character that ended this line.
  These three attributes allow us to assert a new invariant:
  as long as you _modify_ the contents of `line` (e.g.
  turning tabs into spaces),
      info.leading + line + info.trailing + info.end == info.line
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  objects now always have these attributes:
    * `lines`, which contains the base lines iterator.
    * `line`, which contains the original unmodified line.
    * `line_number`, which contains the line number of
      this line.
    * `column_number`, which contains the starting column
      number of the first character of this line.
    * `indent`, which contains the indent level of the
      line if computed, and `None` otherwise.
    * `leading`, which contains the string stripped from
      the beginning of the line.  Initially this is the
      empty string.
    * `trailing`, which contains the string stripped from
      the end of the line.  Initially this is the
      empty string.
    * `end`, which is the end-of-line character
      that ended the current line.  For the last line yielded,
      `info.end` will always be the empty string.  If the last
      character of the text split by `lines` was an end-of-line
      character, the last `line` yielded will be the empty string,
      and `info.end` will also be the empty string.
    * `match`, which contains a `Match` object if this line
      was matched with a regular expression, and `None` otherwise.

  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  also has two new methods:
  [`LineInfo.clip_leading`](#lineinfoclip_leadingline-s)
  and
  [`LineInfo.clip_trailing(line, s)`](#lineinfoclip_trailingline-s).
  These methods clip a leading or
  trailing substring from the current `line`, and transfer
  it to the relevant field in
  [`LineInfo`](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  (either `leading` or
  `trailing`).  `clip_leading` also updates the `column_number`
  attribute.
  The name "clip" was chosen deliberately to be distinct from "strip".
  "strip" functions on strings remove substrings and throws them away;
  my "clip" functions on strings removes substrings and puts them
  somewhere else.
  </dd></dl>

  <dl><dd>
  `lines_filter_comment_lines` has been renamed to
  [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers).
  For backwards compatibility, the function
  is also available under the old name; this old name will
  eventually be removed, but not before September 2025.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  New name for `lines_filter_comment_lines`.
  Correctness improvements:
  [`lines_filter_line_comment_lines`](#lines_filter_line_comment_linesli-comment_markers)
  now enforces that single-quoted strings can't span lines,
  and multi-quoted strings must be closed before the end of
  the last line.
  Minor optimization: for every line, it used to `lstrip` a copy of
  the line, then use a regular expression to see if the line started
  with one of the comment characters.  Now the regular expression
  itself skips past any leading whitespace.
  </dd></dl>

  <dl><dd>
  New feature: [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0-matchmatch)
  has always used `re.search` to examine
  the lines yielded.  It now writes the result to `info.match`.
  (If you pass in `invert=True` to `lines_grep`, `lines_grep`
  still writes to the `match` attribute--but it always writes `None`.)
  If you want to write the `re.Match` object to another attribute,
  pass in the name of that attribute to the keyword-only
  parameter `match`.
  </dd></dl>

  <dl><dd>
  New feature:
  [`lines_rstrip`](#lines_rstripli-separatorsnone)
  and
  [`lines_strip`](#lines_stripli-separatorsnone)
  now both accept a
  `separators` argument; this is an iterable of separators,
  like the argument to
  [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse).
  The default value of `None` preserves the previous behavior,
  stripping whitespace.
  </dd></dl>

  <dl><dd>
  New feature:
  [`lines_sort`](#lines_sortli--keynone-reversefalse)
  now accepts a `key` parameter,
  which is used as the `key` argument for `list.sort`.
  The value passed in to `key` is the `(info, line)` tuple
  yielded by the upstream iterator.  The default value preserves
  the previous behavior, sorting by the `line` (ignoring the
  `info`).
  </dd></dl>

  <dl><dd>
  This function has been renamed
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  and
  rewritten, see below.  The old deprecated version will be
  available at `big.deprecated.lines_strip_comments` until at
  least September 2025.
  Note that the old version of `line_strip_comments` still uses
  the current version of
  [`LineInfo`,](#lineinfolines-line-line_number-column_number--leadingnone-trailingnone-endnone-indent0-matchnone-kwargs)
  so use of this deprecated
  function is still exposed to those breaking changes.
  (For example, `LineInfo.line` now includes the linebreak character
  that terminated the current line, if any.)
  </dd></dl>

  <dl><dd>
  Bugfix:
  [`lines_strip_indent`](#lines_strip_indentli)
  previously required
  whitespace-only lines to obey the indenting rules, which was
  a mistake.  My intention was always for `lines_strip_indent`
  to behave like Python, and that includes not really caring
  about the intra-line-whitespace for whitespace-only
  lines.  Now `lines_strip_indent` behaves more like Python:
  a whitespace-only line behaves as if it has
  the same indent as the previous line.  (Not that the
  indent value of an empty line should matter--but this
  behavior is how you'd intuitively expect it to work.)
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
  is the new name for the old
  `lines_strip_comments` lines modifier function.  It's also
  been completely rewritten.
  Changes:
  * The old function required quote marks and the escape string
    to be single characters. The new function allows quote marks
    and the escape string to be of any length.
  * The old function had a slightly-smelly `triple_quotes` parameter
    to support multiline strings.  The new version supports separate
    parameters for single-line quote marks (`quotes`)  and multiline
    quote marks (`multiline_quotes`).
  * The `backslash` parameter has been renamed to `escape`.
  * The `rstrip` parameter has been removed.  If you need to
    rstrip the line after stripping the comment, wrap your
    `lines_strip_line_comments` call with a
    [`lines_rstrip`](#lines_rstripli-separatorsnone)
    call.
  * The old function didn't enforce that strings shouldn't
    span lines--single-quoted and triple-quoted strings behaved
    identically.  The new version raises `SyntaxError` if quoted
    strings using non-multiline quote marks contain newlines.
  (`lines_strip_line_comments` has always been implemented using
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state);
  this is why it now supports multicharacter
  quote marks and escape strings.  It also benefits from the
  new optimizations in `split_quoted_strings`.)
  </dd></dl>

  <dl><dd>
  Minor optimizations.
  [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
  used to locally define a
  new generator function, then call it and return the generator.
  I promoted the generator function to module level, which means
  we no longer rebind it each time `multisplit` is called.  As
  a very rough guess, this can be as much as a 10% speedup for
  `multisplit` run on very short workloads.  (It's also *never*
  slower.)
  I also applied this same small optimization to several other
  functions in the `text` module.  In particular,
  [`merge_columns`](#merge_columnscolumns-column_separator--overflow_responseoverflowresponseraise-overflow_before0-overflow_after0)
  was binding functions inside a loop (!!).  (Dumb, huh!)
  These local functions are still bound inside `merge_columns`,
  but now at least they're outside the loop.
  Another minor speedup for `multisplit`: when `reverse=True`,
  it used to reverse the results *three times!*  `multisplit`
  now explicitly observes and manages the reversed state of the
  result to avoid needless reversing.
  </dd></dl>

  <dl><dd>
  This function has been renamed
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  and rewritten,
  see below.  The old version is still available, using the name
  `big.deprecated.parse_delimiters` module, and will be available
  until at least September 2025.
  </dd></dl>

  <dl><dd>
  Code cleanups both in the implementation and the test suite,
  including one minor semantic change.
  Cleaned up `Scheduler._next`, the internal method call
  that implements the heart of the scheduler.  The only externally
  visible change: the previous version would call `sleep(0)` every
  time it yielded an event.  On modern operating systems this should
  yields the rest of the current thread's current time slice back
  to the OS's scheduler.  This can make multitasking smoother,
  particularly in Python programs.  But this is too opinionated for
  library code--if you want a `sleep(0)` there, by golly, you can
  call that yourself when the `Scheduler` object yields to you.
  I've restructured the code and eliminated this extraneous `sleep(0)`.
  Also, rewrote big chunks of the test suite (`tests/test_scheduler.py`).
  The multithreaded tests are now much better synchronized, while
  also becoming easier to read.  Although it seems intractable to
  purge *all* race conditions from the test suite, this change has
  removed most of them.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`split_delimiters`](#split_delimiterss-delimiters--state)
  is the new name for the old `parse_delimiters`
  function.  The function has also been completely re-tooled and
  re-written.
  Changes:
  * `parse_delimiters` took an iterable of `Delimiters`
    objects, or strings of length 2.  `split_delimiters`
    takes a dictionary mapping open delimiter strings to
    `Delimiter` objects, and `Delimiter` objects no
    longer have an "open" attribute.
  * `split_delimiters` now accepts an `state` parameter,
    which specifies the initial state of nested delimiters.
  * `split_delimiters` no longer cares if there were unclosed
    open delimiters at the end of the string.  (It used to
    raise `ValueError`.)  This includes quote marks; if you
    don't want quoted strings to span multiple lines, it's up
    to you to detect it and react (e.g. raise an exception).
  * The internal implementation has changed completely.
    `parse_delimiters` manually parsed the input string
    character by character.  `split_delimiters` uses
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    so it zips past the uninteresting characters and only examines
    the delimiters and escape characters.  It's always faster,
    except for some trivial calls (which are fast enough anyway).
  * Another benefit of using `multisplit`: open delimiters,
    close delimiters, and the escape string may now all be
    any nonzero length.  (In the face of ambiguity,
    `split_delimiters` will always choose the longer delimiter.)
  See also changes to `Delimiter`.
  </dd></dl>

  <dl><dd>
  > This API has breaking changes.
  [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
  has been completely re-tooled and
  re-written.  The new API is simpler, easier to understand,
  and conceptually clarified.  It's a major upgrade!
  Changes:
  * The value it yields is different:
    * The old version yielded `(is_quote, segment)`, where
      `is_quote` was a boolean value indicating whether or not
      `segment` was quoted.  If `segment` was quoted, it began
      and ended with (single character) quote marks.  To reassemble
      the original string, join together all the `segment` strings
      in order.
    * The new version yields `(leading_quote, segment, trailing_quote)`,
      where `leading_quote` and `trailing_quote` are either matching
      quote marks or empty.  If they're true values, the `segment`
      string is inside the quotes.  To reassemble the original string,
      join together *all* the yielded strings in order.
  * The `backslash` parameter has been replaced by a new parameter,
    `escape`.  `escape` allows specifying the escape string, which
    defaults to '\\' (backslash).  If you specify a false value,
    there will be no escape character in strings.
  * By default `quotes` only contains `'` (single-quote)
    and `"` (double-quote).  The previous version also
    recognized `"""` and `'''` as multiline quote marks
    by default; this is no longer true, as it's too
    opinionated and Python-specific.
  * The old version didn't actually distinguish between
    single-quoted strings and triple-quoted strings.  It
    simply didn't care whether or not there were newlines
    inside quoted strings.  The new version raises a
    `SyntaxError` if there's a newline character inside
    a string delimited with a quote marker from `quotes`.
  * The old version accepted a stinky `triple_quotes` parameter.
    That's been removed in favor of a new parameter,
    `multiline_quotes`.  `multiline_quotes` is like `quotes`,
    except that newline characters are allowed inside their
    quoted strings.
  * `split_quoted_string` accepts another new parameter,
    `state`, which sets the initial state of quoting.
  * Thd old implementation of `split_quoted_string` used a
    hand-coded parser, manually analyzing each character in
    the input text.  Now it uses
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    so it only bothers to examine
    the interesting substrings.  `multisplit` has a large
    startup cost the first time you use a particular set of
    iterators, but this information is cached for subsequent calls.
    Bottom line, the new version is much faster
    for larger workloads.  (It can be slower for trivial
    examples... where speed doesn't matter anyway.)
  * Another benefit of switching to `multisplit`: `quotes`
    now supports quote delimiters and an escape string
    of any nonzero length.  In the case of ambiguity--if
    more than one quote delimiter matches at a
    time--`split_quoted_string` will always choose the
    longer delimiter.
  </dd></dl>

  <dl><dd>
  New function.
  [`split_title_case`](split_title_cases--split_allcapstrue)
  splits a string at word boundaries,
  assuming the string is in "TitleCase".
  </dd></dl>

  <dl><dd>
  Small performance upgrade for
  [`StateManager`.](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
  observers.
  `StateManager` always uses a copy of the observer
  list (specifically, a tuple) when calling the observers; this
  means it's safe to modify the observer list at any time.
  `StateManager` used to always make a fresh copy every time you
  called an event; now it uses a cached copy, and only recomputes
  the tuple when the observer list changes.
  (Note that it's not thread-safe to modify the observer list
  from one thread while also dispatching events in another.
  Your program won't crash, but the list of observers called
  may be unpredictable based on which thread wins or loses the
  race.  But this has always been true.  As with many libraries,
  the `StateManager` API leaves locking up to you.)
  </dd></dl>
  _p.s. I'm getting close to declaring big as being version 1.0._
  _I don't want to do it until I'm done revising the APIs._
  _p.p.s. Updated copyright notices to 2024._
  _p.p.p.s. Yet again I thank Eric V. Smith for his willingness to humor me
  in my how-many-parameters-could-dance-on-the-head-of-a-pin API theological
  discussions._
  </dd></dl>

  <dl><dd>
  *released 2023/09/19*
  * Breaking change: renamed almost all the old `whitespace` and `newlines` tuples.
    Worse yet, one symbol has the same name but a *different value:* `ascii_whitespace`!
    I've also changed the suffix `_without_dos` to the more accurate and intuitive
    `_without_crlf`, and similarly changed `newlines` to `linebreaks`.
    Sorry for all the confusion.  This resulted from a lot of research into whitespace
    and newline characters, in Python, Unicode, and ASCII; please see the new deep-dive
    [**Whitespace and line-breaking characters in Python and big**](#whitespace-and-line-breaking-characters-in-python-and-big)
    to see what all the fuss is about.  Here's a summary of all the
    changes to the whitespace tuples:
          RENAMED TUPLES (old name -> new name)
            ascii_newlines               -> bytes_linebreaks
            ascii_whitespace             -> bytes_whitespace
            newlines                     -> linebreaks
            ascii_newlines_without_dos   -> bytes_linebreaks_without_crlf
            ascii_whitespace_without_dos -> bytes_whitespace_without_crlf
            newlines_without_dos         -> linebreaks_without_crlf
            whitespace_without_dos       -> whitespace_without_crlf
          REMOVED TUPLES
            utf8_newlines
            utf8_whitespace
            utf8_newlines_without_dos
            utf8_whitespace_without_dos
          UNCHANGED TUPLES (same name, same meaning)
            whitespace
          NEW TUPLES
            ascii_linebreaks
            ascii_whitespace
            str_linebreaks
            str_whitespace
            unicode_linebreaks
            unicode_whitespace
            ascii_linebreaks_without_crlf
            ascii_whitespace_without_crlf
            str_linebreaks_without_crlf
            str_whitespace_without_crlf
            unicode_linebreaks_without_crlf
            unicode_whitespace_without_crlf
  * Changed
    [`split_text_with_code`](#split_text_with_codes--tab_width8-allow_codetrue-code_indent4-convert_tabs_to_spacestrue)
    implementation to use `StateManager`.
    (No API or semantic changes, just an change to the internal implementation.)
  * New function in the [`big.text`](#bigtext) module: [`encode_strings`,](#encode_stringso--encodingascii)
    which takes a container object containing `str` objects and returns an equivalent object
    containing encoded versions of those strings as `bytes`.
  * When you call
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    with a type mismatch
    between 's' and 'separators', the exception it raises
    now includes the values of 's' and 'separators'.
  * Added more tests for `big.state` to exercise all the string arguments
    of `accessor` and `dispatch`.
  * The exhaustive
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    tester now lets you
    specify test cases as cohesive strings, rather
    than forcing you to split the string manually.
  * The exhaustive
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    tester is better at
    internally verifying that it's doing the right
    thing.  (There are some internal sanity checks,
    and those are more accurate now.)
  * Whoops!  The name of the main class in [`big.state`](#bigstate) is
    [`StateManager`.](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
    I accidentally wrote `StateMachine` instead in the docs... several times.
  * Originally the
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    parameter 'separators'
    was required.  I changed it to optional a while ago,
    with a default of `None`.  (If you pass in `None`
    it uses [`big.str_whitespace`](#str_whitespace) or [`big.bytes_whitespace`](#bytes_whitespace),
    depending on the type of `s`.)  But the documentation
    didn't reflect this change until... now.
  * Improved the prose in
    [**The `multi-` family of string functions** deep-dive.](#The-multi--family-of-string-functions)
    Hopefully now it does a better job of selling `multisplit` to the reader.
  * The usual smattering of small doc fixes and improvements.
  My thanks again to Eric V. Smith for his willingness to consider and discuss these
  issues.  Eric is now officially a contributor to **big,** increasing the project's
  [bus factor](https://en.wikipedia.org/wiki/Bus_factor) to two.  Thanks, Eric!

  <dl><dd>
  *released 2023/09/04*
  * Added the new [`big.state`](#bigstate) module, with its exciting
    [`StateManager`](#statemanagerstate--on_enteron_enter-on_exiton_exit-state_classnone)
    class!
  * [`int_to_words`](#int_to_wordsi--flowerytrue-ordinalfalse)
    now supports the new `ordinal` keyword-only parameter, to produce
    *ordinal* strings instead of *cardinal* strings.  (The number 1
    as a *cardinal* string is `'one'`, but as an *ordinal* string is `'first'`).
  * Added the [`pure_virtual`](#pure_virtual) decorator to [`big.builtin`](#bigbuiltin).
  * The documentation is now much prettier!  I finally discovered a syntax
    I can use to achieve a proper indent in Markdown, supported by both
    GitHub and PyPI. You simply nest the text you want indented inside
    an HTML description list as the description text, and skip the
    description item (`<dl><dd>`).  Note that you need a blank
    line after the `<dl><dd>` line, or else Markdown will ignore the
    markup in the following paragraph.  Thanks to Hugo van Kemenade
    for his help confirming this!  Oh, and, Hugo also fixed the image markup
    so the **big** banner displays properly on PyPI.  Thanks, Hugo!
  </dd></dl>

  <dl><dd>
  *released 2023/07/22*
  Extremely minor release.  No new features or bug fixes.
  * Fixed coverage, now back to the usual 100%.
    (This just required changing the tests, which
    *didn't* find any new bugs.)
  * Made the tests for [`Log`](#log-clocknone)
    deterministic.  They now use a fake clock
    that always returns the same values.
  * Added GitHub Actions integration.  Tests and
    coverage are run in the cloud after every checkin.
    Thanks to [Dan Pope](https://github.com/lordmauve)
    for gently walking me through this!
  * Fixed metadata in the `pyproject.toml` file.
  * Added badges for testing, coverage,
    and supported Python versions.
  </dd></dl>

  <dl><dd>
  *released 2023/06/28*
  * Added the new [`big.log`](#biglog) module, with its new
    [`Log`](#log-clocknone) class!
    I wrote this for another project--but it turned out so nice
    I just had to add it to **big**!
  </dd></dl>

  <dl><dd>
  *released 2023/06/15*
  * Bugfix!  If an outer class `Outer` had an inner class `Inner`
    decorated with `@BoundInnerClass`, and `o` is an instance of
    `Outer`, and `o` evaluated to false in a boolean context,
    `o.Inner` would be the *unbound* version of `Inner`.  Now
    it's the bound version, as is proper.
  * Modified `tests/test_boundinnerclasses.py`:
      - Added regression test for the above bugfix (of course!).
      - It now takes advantage of that newfangled "zero-argument `super`".
      - Added testing of an unbound subclass of an unbound subclass.
  </dd></dl>

  <dl><dd>
  *released 2023/06/11*
  * Added
    [`int_to_words`](#int_to_wordsi--flowerytrue-ordinalfalse).
  * All tests now insert the local **big** directory
    onto `sys.path`, so you can run the tests on your
    local copy without having to install.  Especially
    convenient for testing with old versions of Python!
  > *Note:* tomorrow, **big** will be one year old!
  </dd></dl>

  <dl><dd>
  *released 2023/05/19*
  * Convert all iterator functions to use my new approach:
    instead of checking arguments inside the iterator,
    the function you call checks arguments, then has a
    nested iterator function which it runs and returns the
    result.  This means bad inputs raise their exceptions
    at the call site where the iterator is constructed,
    rather than when the first value is yielded by the iterator!
  </dd></dl>

  <dl><dd>
  *released 2023/05/19*
  * Added
    `parse_delimiters` (ed: now [`split_delimiters`](#split_delimiterss-delimiters--state))
    and
    [`Delimiter`.](#delimiteropen-close--backslashfalse-nestedtrue)
  </dd></dl>

  <dl><dd>
  *released 2023/05/18*
  * Major retooling of `str` and `bytes` support in `big.text`.
    * Functions in `big.text` now uniformly accept `str` or `bytes`
      or a subclass of either.  See the
      [Support for bytes and str](#Support-for-bytes-and-str) section
      for how it works.
    * Functions in `big.text` are now more consistent about raising
      `TypeError` vs `ValueError`.  If you mix `bytes` and `str`
      objects together in one call, you'll get a `TypeError`, but
      if you pass in an empty iterable (of a correct type) where
      a non-empty iterable is required you'll get a `ValueError`.
      `big.text` generally tries to give the `TypeError` higher
      priority; if you pass in a value that fails both the type
      check and the value check, the `big.text` function will raise
      `TypeError` first.
  * Major rewrite of
    [`re_rpartition`.](#re_rpartitiontext-pattern-count1--flags0)
    I realized it had the same "reverse mode" problem that
    I fixed in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    back in version **0.6.10**: the regular expression should really
    search the string in "reverse mode", from right to left.
    The difference is whether the regular
    expression potentially matches against overlapping strings.
    When in forwards mode, the regular expression should prefer
    the *leftmost* overlapping match, but in reverse mode it
    should prefer the *rightmost* overlapping match.  Most of the
    time this produces the same list of matches as you'd
    find searching the string forwards--but sometimes the matches come
    out *very* different.
    This was way harder to fix with `re_rpartition` than with `multisplit`,
    because Python's `re` module only supports searching forwards.
    I have to emulate reverse-mode searching by manually checking for
    overlapping matches and figuring out which one(s) to keep--a *lot* of
    work!  Fortunately it's only a minor speed hit if you don't have
    overlapping matches.  (And if you *do* have overlapping matches,
    you're probably just happy `re_rpartition` now produces correct
    results--though I did my best to make it performant anyway.)
    In the future, **big** will probably add support for the
    PyPI package `regex`, which reimplements Python's `re` module
    but adds many features... including reverse mode!
  * New function:
    [`reversed_re_finditer`.](#reversed_re_finditerpattern-string-flags0)
    Behaves almost identically to the Python
    standard library function `re.finditer`, yielding
    non-overlapping matches of `pattern` in `string`.  The difference
    is, `reversed_re_finditer` searches `string` from right to left.
    (Written as part of the
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0)
    rewrite mentioned above.)
  * Added `apostrophes`, `double_quotes`,
    `ascii_apostrophes`, `ascii_double_quotes`,
    `utf8_apostrophes`, and `utf8_double_quotes`
    to the `big.text` module.  Previously the first
    four of these were hard-coded strings inside
    [`gently_title`.](#gently_titles-apostrophesnone-double_quotesnone)
    (And the last two didn't exist!)
  * Code cleanup in `split_text_with_code`, removed redundant code.
    I think it has about the same number of `if` statements; if anything
    it might be slightly faster.
  * Retooled
    [`re_partition`](#re_partitiontext-pattern-count1--flags0)
    and
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0)
    slightly, should now be very-slightly faster.  (Well, `re_rpartition`
    will be slower if your pattern finds overlapping matches.  But at
    least now it's correct!)
  * Lots and lots of doc improvements, as usual.
  </dd></dl>

  <dl><dd>
  *released 2023/03/13*
  * Tweaked the implementation of
    [`multisplit`.](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    Internally, it does the
    string splitting using `re.split`, which returns a `list`.  It used
    to iterate over the list and yield each element.  But that meant keeping
    the entire list around in memory until `multisplit` exited.  Now,
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    reverses the list, pops off the final element, and yields
    that.  This means
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    drops all references to the split strings
    as it iterates over the string, which may help in low-memory situations.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/03/11*
  * Breaking changes to the
    [`Scheduler`](#schedulerregulatordefault_regulator):
    * It's no longer thread-safe by default, which means it's much faster
      for non-threaded workloads.
    * The lock has been moved out of the
      [`Scheduler`](#schedulerregulatordefault_regulator)
      object and into the
      [`Regulator`](#regulator).  Among other things, this
      means that the
      [`Scheduler`](#schedulerregulatordefault_regulator)
      constructor no longer takes a `lock` argument.
    * [`Regulator`](#regulator) is now an abstract base class.
      `big.scheduler` also provides two concrete implementations:
      [`SingleThreadedRegulator`](#singlethreadedregulator)
      and
      [`ThreadSafeRegulator`](#threadsaferegulator).
    * [`Regulator`](#regulator) and
      [`Event`](#eventscheduler-event-time-priority-sequence)
      are now defined in the `big.scheduler` namespace.  They were
      previously defined inside the `Scheduler` class.
    * The arguments to the
      [`Event`](#eventscheduler-event-time-priority-sequence)
      constructor were rearranged.  (You shouldn't care, as you
      shouldn't be manually constructing
      [`Event`](#eventscheduler-event-time-priority-sequence)
      objects anyway.)
    * The `Scheduler` now guarantees that it will only call `now` and `wake`
      on a `Regulator` object while holding that `Regulator`'s lock.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/03/09*
  * Retooled
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    and
    [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
    argument verification code.  Both functions now consistently check all
    their inputs, and use consistent error messages when raising an exception.
  </dd></dl>

  <dl><dd>
  *released 2023/03/09*
  * Fixed a minor crashing bug in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse):
    if you passed in a *list* of separators (or `separators`
    was of any non-hashable type), and `reverse` was true,
    `multisplit` would crash.  It used `separators` as a key
    into a dict, which meant `separators` had to be hashable.
  * [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)
    now verifies that the `s` passed in is either `str` or `bytes`.
  * Updated all copyright date notices to 2023.
  * Lots of doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2023/02/26*
  * Fixed Python 3.6 support! Some equals-signs-in-f-strings and some
    other anachronisms had crept in.  0.6.16 has been tested on all
    versions from 3.6 to 3.11 (as well as having 100% *coverage*).
  * Made the `dateutils` package an optional dependency.  Only one function
    needs it, [`parse_timestamp_3339Z()`](#parse_timestamp_3339zs--timezonenone).
  * Minor cleanup in [`PushbackIterator()`](#pushbackiteratoriterablenone).
    It also uses slots now, which should make it a bit faster.
  </dd></dl>

  <dl><dd>
  *released 2023/01/07*
  * Added the new functions
    [`datetime_ensure_timezone(d, timezone)`](#datetime_ensure_timezoned-timezone) and
    [`datetime_set_timezone(d, timezone)`](#datetime_set_timezoned-timezone).
    These allow you to ensure or explicitly set a timezone on a `datetime.datetime`
    object.
  * Added the `timezone` argument to
    [`parse_timestamp_3339Z()`](#parse_timestamp_3339zs--timezonenone).
  * [`gently_title()`](#gently_titles-apostrophesnone-double_quotesnone)
    now capitalizes the first letter after a left parenthesis.
  * Changed the secret `multirpartition` function slightly.  Its `reverse`
    parameter now means to un-reverse its reversing behavior.  Stated
    another way, `multipartition(reverse=X)` and `multirpartition(reverse=not X)`
    now do the same thing.
  </dd></dl>

  <dl><dd>
  *released 2022/12/11*
  * Improved the text of the `RuntimeError` raised by `TopologicalSorter.View`
    when the view is incoherent.  Now it tells you exactly what nodes are
    conflicting.
  * Expanded the deep dive on `multisplit`.
  </dd></dl>

  <dl><dd>
  *released 2022/12/11*
  * Changed [`translate_filename_to_exfat(s)`](#translate_filename_to_exfats)
    behavior: when modifying a string with a colon (`':'`) *not* followed by
    a space, it used to convert it to a dash (`'-'`).  Now it converts the
    colon to a period (`'.'`), which looks a little more natural.  A colon
    followed by a space is still converted to a dash followed by a space.
  </dd></dl>

  <dl><dd>
  *tagged 2022/12/04*
  * Bugfix: When calling
    [`TopologicalSorter.print()`](#topologicalsorterprintprintprint),
    it sorts the list of nodes, for consistency's sakes and for ease of reading.
    But if the node objects don't support `<` or `>` comparison,
    that throws an exception.  `TopologicalSorter.print()` now catches
    that exception and simply skips sorting.  (It's only a presentation thing anyway.)
  * Added a secret (otherwise undocumented!) function: `multirpartition`,
    which is like
    [`multipartition`](#multipartitions-separators-count1--reverseFalse-separateTrue)
    but with `reverse=True`.
  * Added the list of conflicted nodes to the "node is incoherent"
    exception text.
  > *Note:* although version **0.6.12** was tagged, it was never packaged for release.
  </dd></dl>

  <dl><dd>
  *tagged 2022/11/13*
  * Changed the import strategy.  The top-level **big** module used
    to import all its child modules, and `import *` all the symbols
    from all those modules.  But a friend (hi Mark Shannon!) talked
    me out of this.  It's convenient, but if a user doesn't care about
    a particular module, why make them import it.  So now the top-level
    **big** module contains nothing but a version number, and you
    can either import just the submodules you need, or you can import
    **big.all** to get all the symbols (like **big** itself used to do).
  > *Note:* although version **0.6.11** was tagged, it was never packaged for release.
  </dd></dl>

  <dl><dd>
  *released 2022/10/26*
  * All code changes had to do with
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse):
      * Fixed a subtle bug.  When splitting with a separator that can overlap
        itself, like `' x '`, `multisplit` will prefer the *leftmost* instance.
        But when `reverse=True`, it must prefer the *rightmost* instance.
        Thanks to Eric V. Smith for suggesting the clever "reverse everything,
        call `re.split`, and un-reverse everything" approach.  That let me
        fix this bug while still implementing on top of `re.split`!
      * Implemented `PROGRESSIVE` mode for the `strip` keyword.  This behaves
        like `str.strip`: when splitting, strip on the left, then start splitting.
        If we don't exhaust `maxsplit`, strip on the right; if we *do* exhaust
        `maxsplit`, *don't* strip on the right.  (Similarly for `str.rstrip`
        when `reverse=True`.)
      * Changed the default for `strip` to `False`.  It used to be
        `NOT_SEPARATE`.  But this was too surprising--I'd forget that it
        was the default, and turning on `keep` wouldn't return everything I
        thought I should get, and I'd head off to debug `multisplit`, when in
        fact it was behaving as specified.  The Principle Of Least Surprise
        tells me that `strip` defaulting to `False` is less surprising.
        Also, maintaining the invariant that all the keyword-only parameters
        to `multisplit` default to `False` is a helpful mnemonic device in
        several ways.
      * Removed `NOT_SEPARATE` (and the not-yet-implemented `STR_STRIP`)
        modes for `strip`.  They're easy to implement yourself, and this
        removes some surface area from the already-too-big
        `multisplit` API.
  * Modernized `pyproject.toml` metadata to make `flit` happier.  This was
    necessary to ensure that `pip install big` also installs its dependencies.
  </dd></dl>

  <dl><dd>
  *released 2022/10/16*
  * Renamed two of the three freshly-added lines modifier functions:
    `lines_filter_contains` is now
    [`lines_containing`](#lines_containingli-s--invertfalse),
    and `lines_filter_grep` is now
    [`lines_grep`](#lines_grepli-pattern--invertfalse-flags0-matchmatch).
  </dd></dl>

  <dl><dd>
  *released 2022/10/16*
  * Added three new lines modifier functions
    to the [`text`](#bigtext) module:
    `lines_filter_contains`,
    `lines_filter_grep`,
    and
    [`lines_sort`.](#lines_sortli--keynone-reversefalse)
  * [`gently_title`](#gently_titles-apostrophesnone-double_quotesnone)
    now accepts `str` or `bytes`.  Also added the `apostrophes` and
    `double_quotes` arguments.
  </dd></dl>

  <dl><dd>
  *released 2022/10/14*
  * Fixed a bug in
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse).
    I thought when using `keep=AS_PAIRS` that it shouldn't ever emit a 2-tuple
    containing just empty strings--but on further reflection I've realized that
    that's correct.  This behavior is now tested and documented, along with
    the reasoning behind it.
  * Added the `reverse` flag to
    [`re_partition`](#re_partitiontext-pattern-count1--flags0-reversefalse).
  * `whitespace_without_dos` and `newlines_without_dos` still had the DOS
    end-of-line sequence in them!  Oops!
      * Added a unit test to check that.  The unit test also ensures that
        `whitespace`, `newlines`, and all the variants (`utf8_`, `ascii_`,
        and `_with_dos`) exactly match the set of characters Python considers
        whitespace and newline characters.
  * Lots more documentation and formatting fixes.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  * Added the new [`itertools`](#bigitertools) module, which so far only contains
    [`PushbackIterator`](#pushbackiteratoriterablenone).
  * Added
    `lines_strip_comments` [ed: now [`lines_strip_line_comments`](#lines_strip_line_commentsli-line_comment_markers--quotes-escape-multiline_quotes)
    and
    [`split_quoted_strings`](#split_quoted_stringss-quotes---escape-multiline_quotes-state)
    to the
    [`text`](#bigtext)
    module.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  * I realized that [`whitespace`](#whitespace) should contain the DOS end-of-line
    sequence (`'\r\n'`), as it should be considered a single separator
    when splitting etc.  I added that, along with [`whitespace_no_dos`](#whitespace),
    and naturally [`utf8_whitespace_no_dos`](#whitespace) and
    [`ascii_whitespace_no_dos`](#whitespace) too.
  * Minor doc fixes.
  </dd></dl>

  <dl><dd>
  *released 2022/10/13*
  A **big** upgrade!
  * Completely retooled and upgraded
    [`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse),
    and added
    [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
    and
    [`multipartition`,](#multipartitions-separators-count1--reverseFalse-separateTrue)
    collectively called
    [**The `multi-` family of string functions.**](#The-multi--family-of-string-functions)
    (Thanks to Eric Smith for suggesting
    [`multipartition`!](#multipartitions-separators-count1--reverseFalse-separateTrue)
    Well, sort of.)
    * `[`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)`
      now supports five (!) keyword-only parameters, allowing the caller
      to tune its behavior to an amazing degree.
    * Also, the original implementation of
      `[`multisplit`](#multisplits-separatorsnone--keepfalse-maxsplit-1-reversefalse-separatefalse-stripfalse)`
      got its semantics a bit wrong; it was inconsistent and maybe a little buggy.
    * [`multistrip`](#multistrips-separators-leftTrue-rightTrue)
      is like `str.strip` but accepts an iterable of
      separator strings.  It can strip from the left, right, both, or
      neither (in which case it does nothing).
    * [`multipartition`](#multipartitions-separators-count1--reverseFalse-separateTrue)
      is like `str.partition`, but accepts an iterable
      of separator strings.  It can also partition more than once,
      and supports `reverse=True` which causes it to partition from the right
      (like `str.rpartition`).
    * Also added useful predefined lists of separators for use with all
      the `multi` functions: [`whitespace`](#whitespace) and [`newlines`](#newlines),
      with `ascii_` and `utf8_` versions of each, and `without_dos` variants of
      all three [`newlines`](#newlines) variants.
  * Added the
    [`Scheduler`](#schedulerregulatornone)
    and
    [`Heap`](#heapinone)
    classes.  [`Scheduler`](#schedulerregulatornone)
    is a replacement for Python's `sched.scheduler` class, with a modernized
    interface and a major upgrade in functionality.  [`Heap`](#heapinone)
    is an object-oriented interface to Python's `heapq` module, used by
    [`Scheduler`](#schedulerregulatornone).
    These are in their own modules, [`big.heap`](#bigheap) and [`big.scheduler`](#bigscheduler).
  * Added
    [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
    and all the `lines_` modifiers.  These are great for writing little text parsers.
    For more information, please see the deep-dive on
    [**`lines` and lines modifier functions.**](#lines-and-lines-modifier-functions)
  * Removed`stripped_lines` and `rstripped_lines` from the `text` module,
    as they're superceded by the far superior
    [`lines`](#liness-separatorsnone--line_number1-column_number1-tab_width8-kwargs)
    family.
  * Enhanced
    [`normalize_whitespace`](#normalize_whitespaces-separatorsNone-replacementNone).
    Added the `separators` and `replacement` parameters,
    and added support for `bytes` objects.
  * Added the `count` parameter to
    [`re_partition`](#re_partitiontext-pattern-count1--flags0)
    and
    [`re_rpartition`](#re_rpartitiontext-pattern-count1--flags0).
  </dd></dl>

  <dl><dd>
  *released 2022/09/12*
  * Added `stripped_lines` and `rstripped_lines` to the [`text`](#bigtext) module.
  * Added support for `len` to the [`TopologicalSorter`](#topologicalsortergraphnone) object.
  </dd></dl>

  <dl><dd>
  *released 2022/09/04*
  * Added
    [`gently_title`](#gently_titles-apostrophesnone-double_quotesnone)
    and
    [`normalize_whitespace`](#normalize_whitespaces-separatorsNone-replacementNone)
    to the [`text`](#bigtext) module.
  * Changed [`translate_filename_to_exfat`](#translate_filename_to_exfats)
    to handle translating `':'` in a special way.
    If the colon is followed by a space, then the colon is turned into `' -'`.
    This yields a more natural translation when colons are used in text, e.g.
    `'xXx: The Return Of Xander Cage'` is translated to `'xXx - The Return Of Xander Cage'`.
    If the colon is not followed by a space, turns the colon into `'-'`.
    This is good for tiresome modern gobbledygook like `'Re:code'`, which
    will now be translated to `'Re-code'`.
  </dd></dl>

  <dl><dd>
  *released 2022/06/12*
  * Initial release.
  </dd></dl>


pkgId: 30446c79c8ba3a05a82c200bb6ea14cb1716ee97a53e49ff12290e938faeab38
name: python-big-o-estimator
arch: src
version: 0.1.3.2
epoch: 0
release: 1
summary: A package to estimate Big-O complexity of Python functions.
url: https://github.com/amukherjee1991/big_o_estimator_package
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-big-o-estimator-0.1.3.2-1.src.rpm
checksum_type: sha256
description:
  This Python package allows you to estimate the Big-O complexity of Python functions using AST (Abstract Syntax Tree) parsing.



  You can install the package using:

  ```bash
  pip install .
  ```

  Usage

  ```python
  from big_o_estimator import get_big_o_of_function

  def sample_function(n):
      for i in range(n):
          pass

  print(get_big_o_of_function(sample_function))
  ```


pkgId: b876fd291321deb734ab5d28c03288c2ae99f0c8ec83a13b0976b32a2c92f216
name: python-big-query-kusto-client
arch: src
version: 0.3.0
epoch: 0
release: 1
url: https://github.com/jjtriff/big-query-kusto-client
rpm_license: Apache-2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-big-query-kusto-client-0.3.0-1.src.rpm
checksum_type: sha256
summary:
  Wrapper around azure-kusto client to facilitate bringing BIG query results while avoiding hitting the limits

description:
  Package to facilitate the gathering of big datasets
  from Azure Data Explorer by paginating



  ```python
  from bigquerykustoclient import BigQueryKustoClient


  kusto = build_kusto_client()




  with BigQueryKustoClient(kusto) as client:
      df: pandas.DataFrame = client.execute_query(
          db='ContosoSales',
          query= 'SaltesTable | order by DateKey, ProductKey, CustomerKey',
          optimal_page=True
      )

  len(df)  # Will give you the amount of rows it broght for you
  ```



  For this to work it is required that the query imposes an
  order on the results, no matter what column you use but an
  `| order by ` operator must be in the query.



  The package uses this other values from the system for its
  configuration. It tries to keep sensible defaults:

  ```properties
  ADX_RECORDS_LIMIT=500000  # Limit of amount of rows in ADX
  ADX_SIZE_IN_BYTES_LIMIT=67108864  # 64MB limit of size of result
  BQKC_PAGE_SIZE=100000  # Default pagesize that we'll use to paginate the results
  BQKC_SQ_PREFIX=BigQueryKustoClient  # A prefix that will be used as namespace for the queries
  ```

  `BQKC_PAGE_SIZE` will get overridden if the parameter `optimal_page=True` is used.
  When done so, the pacakge will try to determine the biggest possible size of page
  to use.


pkgId: 7e97e1dc3c24894f393d3b900036dc9f28460ba53cc304afc54b85d881edf9e6
name: python-big-thing-py
arch: src
version: 0.4.2.0.post3
epoch: 0
release: 1
summary: MySSIX Thing SDK
url: https://pypi.org/project/big-thing-py/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-big-thing-py-0.4.2.0.post3-1.src.rpm
checksum_type: sha256
description:
  [![PyPI version](https://badge.fury.io/py/big-thing-py.svg)](https://badge.fury.io/py/big-thing-py)

  이 문서에서는 `MySSIX IoT Platform`의 Thing Python SDK를 소개합니다. `Thing`은 `MySSIX IoT Platform`에서 사용되는 기능의 단위인 `Service`를 제공하는 제공자입니다. 이 문서를 통해 `Thing`을 실행하고 개발하는 방법에 대해 알 수 있습니다.

  `MySSIX IoT Platform`에서 `Thing`은 크게 2가지로 나뉩니다.

  1. **Big Thing**
  2. **Small Thing**

  이 문서에서는 Python으로 작성된 **Big Thing** 어플리케이션을 실행하고 개발하는 방법에 대해 설명합니다. **Small Thing**에 대해서는 [Small Thing](https://github.com/sopiot/small-thing)을 참고해주세요.





  해당 SDK를 사용하기 위해서는 다음과 같은 요구사항을 만족해야합니다.

  - Python 3.7+



  ```bash
  pip3 install big-thing-py
  ```



  ```bash
  git clone https://github.com/sopiot/big-thing-py.git
  cd big-thing-py
  pip3 install .



  ```



  예제를 실행하기 전에 `MySSIX IoT Platform`의 `Middleware`를 먼저 실행해야 합니다.



  ```bash
  docker pull sopiot/sopiot_middleware_ubuntu1804_x64
  docker run -it -d --name sopiot_middleware -p 1883:1883 -p 1884:1884 -p 8883:8883 -p 8884:8884 -p 58132:58132 sopiot/sopiot_middleware_ubuntu1804_x64
  ```

  이후, 다음의 명령어를 통해 기본 예제를 실행할 수 있습니다.

  ```bash
  python3 samples/big_thing/basic_test/run.py
  ```

  만약 가상환경을 생성하여 예제를 실행하려는 경우 `pipenv`를 사용할 수 있습니다. 우선, 다음 명령어로 `pipenv`를 설치합니다.

  ```bash
  pip install pipenv --upgrade
  ```

  `pipenv` 가상환경을 생성 및 레포지토리로부터 패키지를 설치합니다.

  ```bash
  pipenv install -e .
  ```

  그리고, 다음의 명령어를 통해 기본 예제를 실행할 수 있습니다.

  ```bash
  pipenv run python samples/big_thing/basic_test/run.py
  ```

  `Thing`이 `Middleware`에 등록되면 `Thing`의 `Service`를 `Scenario`를 통해 사용할 수 있습니다. `Scenario`는 `MySSIX IoT Platform`의 [Web Client App](http://iotdev.snu.ac.kr:3014/)을 통해 조작 및 생성할 수 있습니다. -> _따로 Documents 폴더를 생성하여 Web Client App와 관련한 README를 작성해야하는 것이 좋아보임_.



  [Samples README](samples/README.md)


pkgId: 74e2123f82b42f81b40e11c9882638e972714f2f83dcbcf6b2f6ee593f1c6027
name: python-bigants
arch: src
version: 2.2.2
epoch: 0
release: 1
summary: BiGAnts - a package for network-constrained biclustering of omics data
url: https://github.com/biomedbigdata/BiGAnts-PyPI-package
rpm_license: GNU General Public License v3 (GPLv3)
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigants-2.2.2-1.src.rpm
checksum_type: sha256
description:
  * [General info](#general-info)
  * [Installation](#installation)
  * [Data input](#data-input)
  * [Main functions](#main-functions)
  * [Example](#example)
  * [Quality control](#quality-control)
  * [Cite](#cite)
  * [Contact](#contact)



  Unsupervised learning approaches are frequently employed to identify patient subgroups and biomarkers such as disease-associated genes. Biclustering is a powerful technique often used with expression data to cluster genes along with patients. However, the genes forming biclusters are often not functionally related, complicating interpretation of the results.

  To alleviate this, we developed the network-constrained biclustering approach BiGAnts which **(i)** restricts biclusters to functionally related genes connected in molecular interaction networks and **(ii)** maximizes the expression difference between two subgroups of patients.

  ![alt text](https://github.com/biomedbigdata/BiGAnts-PyPI-package/blob/master/img/schema.png?raw=true)




  To install the package from PyPI please run:

  `pip install bigants`

  To install the package from git:

  `git clone https://github.com/biomedbigdata/BiGAnts-PyPI-package`

  `python setup.py install`




  The algorithm needs as an input one CSV matrix with gene expression/methylation/any other numerical data and one CSV file with a network.

  Please note, that even though the algorithm will accept any IDs, all visualisation tools except entrez genes IDs as an input.



  Numerical data is accepted in the following format:
  - genes as rows.
  - patients as columns.
  - first column - genes IDs (can be any IDs).

  For instance:

  | Unnamed: 0 | GSM748056 | GSM748059 | ... | GSM748278 | GSM748279 | GSM1465989 |
  |------------|-----------|-----------|-----|-----------|-----------|------------|
  | 1454       | 0.053769  | 0.117412  | ... | -0.392363 | -1.870838 | -1.432554  |
  | 201931     | -0.618279 | 0.278637  | ... | 0.803541  | -0.514947 | 2.361925   |
  | 8761       | 0.215820  | -0.343865 | ... | 0.700430  | 0.073281  | -0.977656  |
  | 2703       | -0.504701 | 1.295049  | ... | 1.861972  | 0.601808  | 0.191013   |
  | 26207      | -0.626415 | -0.646977 | ... | 2.331724  | 2.339122  | -0.100924  |

  There are 2 examples of gene expression datasets that can be placed in the "data" folder
  - GSE30219 - a Non-Small Cell Lung Cancer dataset from GEO for patients with either adenocarcinoma or squamous cell carcinoma.
  - TCGA pan-cancer dataset with patients that have luminal or basal breast cancer.
  Both can be found [here](https://drive.google.com/drive/folders/1J0XRrklwcV_Cgy_9Ay_6yJrN_x28Cosk?usp=sharing)



  An interaction network should be present as a CSV table with two columns that represent two interacting genes. **Without a header!**

  For instance:

  | 6416 | 2318 |
  |------|------|
  | 6416 | 5371 |
  | 6416 | 351  |
  | 6416 | 409  |
  | 6416 | 5932 |
  | 6416 | 1956 |

  There is an example of a PPI network from BioiGRID with experimentally validated interactions [here](https://drive.google.com/drive/folders/1J0XRrklwcV_Cgy_9Ay_6yJrN_x28Cosk?usp=sharing).



  Here we give a general description of the main functions provided. Please note, that all functions are annotated with dockstrings and therefore the full information can be found with *help()* method, e.g. `help(results.save)`.

  1.**data_preprocessing**(path_expr, path_net, log2 = False, size = 2000)

  Parameters:

  - path_to_expr: *string*, path to the numerical data
  - path_to_net: *string*, path to the network file
  - log2: *bool, (default = False)*, indicates if log2 transformation should be applied to the data
  - size: *int, optional (default = 2000)* determines the number of genes that should be pre-selected by variance for the analysis. Shouldn't be higher than 5000.
  - no_zero: (default - none) indicate the fraction of allowed non-zero values for each patient. For instance no_zero = 0.8 means that all genes which have no expression for at least 80% of patients will be removed from the analysis

  Returns:

  - GE: *pandas data frame*, processed expression data
  - G: *networkX graph*, processed network data
  - labels: *dict*, for mapping between real genes/patients IDs and the internal ones
  - rev_labels: *dict*, additional dictionary for mapping between real genes/patients IDs and the internal ones

  2. *BiGAnts**(GE,G,L_g_min,L_g_max) creates a model for the given data:

  Parameters:

  - GE: *pandas dataframe*, processed expression data
  - G: *networkX graph*, processed network data
  - L_g_min: *int*, minimal solution subnetwork size
  - L_g_max: *int*, maximal solution subnetwork size

  Methods:

  BiGAnts.**run**(self, n_proc = 1, K = 20, evaporation = 0.5, show_plot = False)

  - K: *int, default = 20*, number of ants. Fewer ants - less space exploration. Usually set between 20 and 50
  - n_proc: *int, default = 1*, number of processes that should be used(can not be more than K)
  - evaporation, *float, default = 0.5*, the rate at which pheromone evaporates
  - show_plot: *bool, default = False*, set true if convergence plots should be shown during the iterations



  Import the package:

  ```python
  from bigants import data_preprocessing
  from bigants import BiGAnts
  from bigants import results_analysis
  ```
  Set the paths to the expression matrix and the PPI network:

  ```python
  path_expr,path_net ='/data/gse30219_lung.csv', '/data/biogrid.human.entrez.tsv'
  ```
  Load and process the data:

  ```python
  GE,G,labels, _= data_preprocessing(path_expr, path_net)
  ```
  Set the size of subnetworks:
  ```python
  L_g_min = 10
  L_g_max = 15
  ```
  Set the model and run the search:

  ```python
  model = BiGAnts(GE,G,L_g_min,L_g_max)
  solution,scores= model.run_search()
  ```

  BiGAnts package also allows a user to save the results and perform an initial analysis.
  The examples below show the basic usage, for more details please use python help() method, e.g. `help(results.save)`.

  1. First, the object for results analysis must be created:
  ```python
  results = results_analysis(solution, labels)
  ```
  This will allow to easily access the resulting biclusters and their initial IDs as well as perform a more complicated analysis.

  To access IDs of patients in the first bicluster:
  ```python
  results.patients1
  ```
  To access IDs of genes IDs in the first bicluster:
  ```python
  results.genes1
  ```
  Same logic applies to the second bicluster.

  If in the further analysis you would like to use gene names, please set 'convert' to True and specify the original gene IDs, i.e.:
  ```python
  results = results_analysis(solution, labels, convert = True, origID = 'entrezgene')
  ```
  Some other options for the original gene ID: ensembl.gene', 'symbol', 'refseq', 'unigene', etc
  For all possibe option please check  the reference for MyGene.info gene query [web service](            http://docs.mygene.info/en/latest/doc/query_service.html#available_fields)

  2. To save the solution:

  ```python
  results.save(output = "results/results.csv")
  ```

  3. Visualise the resulting networks colored with respect to their difference in expression patterns in patients clusters:
  ```python
  results.show_networks(GE, G, output = "results/network.png")
  ```

  4. Visualise a clustermap of the achieved solution alone or also along with the known patients' groups.
  Just with the BiGAnts results:

  ```python
  results.show_clustermap(GE, G, solution, labels, output = "results/clustermap.png")
  ```
  If you have a patient's phenotype you would like to use for comparison, please make sure that patients IDs are exactly (!) matching the IDs that were used as an input. The IDs should be represented as a list of two lists, e.g.:

  ```python
  true_classes = ['GSM748056', 'GSM748059',..], ['GSM748278', 'GSM748279', 'GSM1465989']
  results.show_clustermap(GE, G, solution, labels, output = "results/clustermap.png", true_labels = true_classes)
  ```

  5. Given a known phenotype in a format described above, BiGAnts can also return Jaccard index of the achieved patients clustering with a given phenotype:

  ```python
  results.jaccard_index(true_labels = true_classes)
  ```

  6. BiGAnts is using [gseapy](https://gseapy.readthedocs.io/en/master/index.html) module to provide a user with a python wrapper for [Enrichr](https://amp.pharm.mssm.edu/Enrichr/) database.

  ```python
  results.enrichment_analysis(solution, labels, library = 'GO_Biological_Process_2018', "results")
  ```

  After the execution of the given above code, in the */results* directory a user can find a table with enriched pathways as well as enrichment plots. Other available libraries can be used as well, e.g. 'GO_Molecular_Function_2018' and 'GO_Cellular_Component_2018'. In total there are 159 libraries available at the moment and the full list can be found by typing:

  ```python
  import gseapy
  gseapy.get_library_name()
  ```



  The best way to check if the algorithm produced high-quality results and there are no issues with the parameters is to analyse the convergence plot:

  ```python
  results.convergence_plot(scores)
  ```
  * The algorithm converged:

  1. If the maximum score has stabilised for several iterations in a row (default is 6).

  OR

  2. If the average score became equal (or nearly equal) to the maximal score:

  ![text](https://github.com/biomedbigdata/BiGAnts-PyPI-package/blob/master/img/conv1.png?raw=true)

  * The algorithm did not converge:

  If the average and the maximal score improve over the iterations but do not stabilize then just increase the number of maximally allowed iterations:

  ![text](https://github.com/biomedbigdata/BiGAnts-PyPI-package/blob/master/img/conv2.png?raw=true)

  If the scores do not stabilize even after 60-100 iterations, please contact us.


  If you got the following error message:
  ```python
  AssertionError: bad probability update
  ```
  It can mean one of the following issues:
  1. The setting of the algorithm is way to restrictive for your problem. You can try to fix it by repeating the analysis with th = 0, or even th = -1 e.g.:

  ```python
  model = BiGAnts(GE,G,L_g_min,L_g_max)
  solution,scores= model.run_search(th = 0)

  ```
  2. Otherwise, the problem might be related to the way you have processed your data. Please make sure that you do not have not expressed genes for the magority of the patients, that you log2 or even log10 scaled your values.



  BiGants was developed by the [Big Data in BioMedicine group](biomedical-big-data.de) and [Computational Systems Medicine group](https://compsysmed.de/) at [Chair of Experimental Bioinformatics](https://www.baumbachlab.net/).

  If you use BiGAnts in your research, we kindly ask you to cite the following manuscript:
  ` Lazareva, O., Van Do, H., Canzar, S., Yuan, K., Baumbach, J., Kacprowski, T., List, M.: BiGAnts: Network-constrained biclustering of patients and omics data. [Submitted]`


  If you have difficulties using BiGAnts, please open an issue at out [GitHub](https://github.com/biomedbigdata/BiGAnts-PyPI-package) repository. Alternatevely, you can write an email to:
  * [Olga Lazareva](mailto:olga.lazareva@wzw.tum.de?subject=[BiGAnts-PyPI]%20BiGAnts%20WEB)
  * [Markus List](mailto:markus.list@wzw.tum.de?subject=[BiGAnts-PyPI]%20BiGAnts%20WEB)
  * [Tim Kacprowski](mailto:tim.kacprowski@wzw.tum.de?subject=[BiGAnts-PyPI]%20BiGAnts%20WEB)


pkgId: 55a601f72a30740ea4c8b8d987181a363f7f57275f13d68d5546e775edc053d1
name: python-bigbird
arch: src
version: 0.1.2
epoch: 0
release: 1
summary: Genetic Algorithms in Python
url: https://github.com/s2011r2593/big-bird
rpm_license: MIT License
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigbird-0.1.2-1.src.rpm
checksum_type: sha256
description:
  Big Bird is a (hopefully useful) package designed to facilitate the quick and convenient creation of basic genetic algorithms in python.


  ```python
  import bigbird


  pop_size = 2000             # Size of each generation's population
  layer_counts = [8, 5, 5, 3] # No. of nodes in each layer
  m_r8 = 0.3                  # Chance of weight mutation
  step_ratio = 1/3.6          # Size of weight perturbation
  reinit_r = 0.015            # Chance of weight reinitialization on mutation
  immune = 20                 # No. of non-mutated from top of previous generation


  max_generation = 300


  population = bigbird.SimplePopulation(pop_size, layer_counts)

  for generation in range(max_generation):
      for bird in population.birds:
          
          bird_inp = your_input_generation_fn()
          bird_out = bird.eval(bird_inp)
          decision = output.tolist().index(max(output))
          bird.fitness = your_fitness_fn(decision)

      
      champ = sorted(population.birds, key=lambda x: x.fitness)[-1]
      champ.save(fname='./hall-of-fame/champ-' + str(generation) + '.json')

      
      population.store(immune)
      population.breed()
      population.mutate(m_r8, step_ratio, reinit=reinit_r)
      population.retrieve()
  ```

  Find more examples [here](https://github.com/s2011r2593/big-bird/tree/main/examples)


  [MIT](https://choosealicense.com/licenses/mit/)


pkgId: c9f0360d546691690e72a2a01526ce81f0e73859b65583e2bdca604b50c76a1e
name: python-bigbluebutton
arch: src
version: 0.6.0
epoch: 0
release: 1
summary: Python API for bigbluebutton.
url: https://hg.sr.ht/~dreimark/bigbluebutton-python-api
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigbluebutton-0.6.0-1.src.rpm
checksum_type: sha256
description:
  The work of this project is derivated from https://github.com/schallis/django-bigbluebutton 98f2259fa3 by Steve Challis.
  It is a wrapper for accessing the API of bigbluebutton http://code.google.com/p/bigbluebutton/wiki/API
  A simple example:
     import argparse
     from bigbluebutton import MeetingSetup, Meeting
     import bbb_settings
     if __name__ == '__main__':
         PARSER = argparse.ArgumentParser(description='creates and join a session')
         PARSER.add_argument('--meeting_name', dest="meeting_name", type=str, required=True,
                              help='name of the meeting')
         PARSER.add_argument('--meeting_id', dest='meeting_id', required=True,
                             help='id for the meeting')
         PARSER.add_argument('--moderator', dest='moderator', required=True,
                             help='name of the meeting moderator')
         PARSER.add_argument( '--moderator_password', dest='moderator_password', required=True,
                             help='password for moderator')
         PARSER.add_argument( '--attendee_password', dest='attendee_password', required=True,
                             help='password for attendee')
         PARSER.add_argument( '--url', dest='url', required=True,
                             help='pre upload url')
         ARGS = PARSER.parse_args()
         session = MeetingSetup(bbb_settings.BBB_API_URL, bbb_settings.SALT,
                                ARGS.meeting_name, ARGS.meeting_id,
                                ARGS.attendee_password, ARGS.moderator_password,
                                pre_upload_slide=ARGS.url)
         session.create_meeting()
         print("meeting expires if noone joins in")
         meeting = Meeting(bbb_settings.BBB_API_URL, bbb_settings.SALT)
         print("MODERATOR:")
         print(meeting.join_url(ARGS.meeting_id, ARGS.moderator, ARGS.moderator_password))
         print('-------------------------------------------')
         print("RANDOM USER:")
         print(meeting.join_url(ARGS.meeting_id, 'RANDOM', ARGS.attendee_password))
         print('-------------------------------------------')
         print("ALL MEETINGS")
         print(meeting.get_meetings())
         print('-------------------------------------------')
         print("IS RUNNING (meeting is only running after someone has joined in)")
         print(meeting.is_running(ARGS.meeting_id))
         print('-------------------------------------------')
         print("END MEETING URL")
         print(meeting.end_meeting_url(ARGS.meeting_id, ARGS.moderator_password))
         print('-------------------------------------------')
         if meeting.is_running(ARGS.meeting_id):
             print("END MEETING")
             meeting.end_meeting(ARGS.meeting_id, ARGS.moderator_password)
             print('-------------------------------------------')


pkgId: 5dfd98afd8ed0e1ab10858bc4fba66cb118393bc72257eeead38634d61848425
name: python-bigboat
arch: src
version: 1.0.1
epoch: 0
release: 1
summary: BigBoat docker dashboard API
url: https://gros.liacs.nl
rpm_license: Apache 2.0
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigboat-1.0.1-1.src.rpm
checksum_type: sha256
description:
  [![PyPI](https://img.shields.io/pypi/v/bigboat.svg)](https://pypi.python.org/pypi/bigboat)
  [![Build
  status](https://github.com/grip-on-software/bigboat-python-api/actions/workflows/bigboat-python-api.yml/badge.svg)](https://github.com/grip-on-software/bigboat-python-api/actions/workflows/bigboat-python-api.yml)
  [![Coverage
  Status](https://coveralls.io/repos/github/grip-on-software/bigboat-python-api/badge.svg?branch=master)](https://coveralls.io/github/grip-on-software/bigboat-python-api?branch=master)
  [![Quality Gate
  Status](https://sonarcloud.io/api/project_badges/measure?project=grip-on-software_bigboat-python-api&metric=alert_status)](https://sonarcloud.io/project/overview?id=grip-on-software_bigboat-python-api)

  Python wrapper library for the BigBoat API. This API can create, retrieve,
  update and delete application definitions, do similar operations for instances
  and poll for status.

  Support for v2 and the deprecated v1 APIs (limited to certain operations) is
  included. Note that BigBoat development itself has halted.



  The BigBoat Python API has been tested to work on Python 3.8+. The API has few
  dependencies; see `requirements.txt` for the list of installation requirements.
  The short list is also repeated here:

  - [Requests](http://docs.python-requests.org/en/master/user/install/)
  - [PyYAML](http://pyyaml.org/wiki/PyYAMLDocumentation)



  Install the latest version from PyPI using:

  ```
  pip install bigboat
  ```

  For local development, you can use `pip install .` to install the package from
  the cloned Git repository.



  First, import the library:

  ```python
  import bigboat
  ```

  Next, determine the URL of your BigBoat instance. In this example we use
  `http://BIG_BOAT`. Also check whether to use the v1 or v2 version of the API.
  v1 is limited (deprecated in newer versions) and v2 requires an API key.
  Example:

  ```python
  api = bigboat.Client_v2('http://BIG_BOAT', 'MY_API_KEY')
  ```

  You can then use various methods on the client API, namely:
  - `api.apps()`: List of Applications
  - `api.get_app(name, version)`: Retrieve a specific Application
  - `api.update_app(name, version)`: Register an Application
  - `api.delete_app(name, version)`: Delete an Application
  - `api.instances()`: List of Instances
  - `api.get_instance()`: Retrieve a specific Instance
  - `api.update_instance(name, app_name, version, ...)`: Start an Instance
  - `api.delete_instance(name)`: Stop an Instance

  In addition to the common methods, v2 has the following API methods:
  - `api.get_compose(name, version, file_name)`: Retrieve a docker compose or
    bigboat compose file for an Application
  - `api.update_compose(name, version, file_name, content)`: Update a docker
    compose or bigboat compose file for an Application
  - `api.statuses()`: Retrieve a list of status dictionaries



  - [GitHub
    Actions](https://github.com/grip-on-software/bigboat-python-api/actions) is
    used to run unit tests and report on coverage for commits and pull requests.
  - [SonarCloud](https://sonarcloud.io/project/overview?id=grip-on-software_bigboat-python-api)
    performs quality gate scans and tracks them.
  - [Coveralls](https://coveralls.io/github/grip-on-software/bigboat-python-api)
    receives coverage reports and tracks them.
  - You can perform local lint checks, typing checks, tests and coverage during
    development (after setting up a local [installation](#installation)) with
    `make pylint`, `make mypy`, `make test` and `make coverage`, respectively,
    after installing dependencies from `requirements-analysis.txt` by running
    `make setup_analysis` (for the `pylint` and `mypy` recipes) and from
    `requirements-test.txt` with `make setup_test` (necessary for making all the
    Makefile recipes mentioned here function correctly).
  - We publish releases to [PyPI](https://pypi.python.org/pypi/bigboat) using
    `make release` which performs multiple checks: version number consistency,
    lint, typing and unit tests.
  - Noteworthy changes to the module are added to the [changelog](CHANGELOG.md).



  The API wrapper library is licensed under the Apache 2.0 License.



  - [Docker Dashboard](https://github.com/ICTU/docker-dashboard)


pkgId: 1ee7cacb6e12b17e148dafd71ce71f440c603a4fc402b9046fd2d62669ca7e73
name: python-bigcommerce
arch: src
version: 0.23.4
epoch: 0
release: 1
summary: Connect Python applications with the Bigcommerce API
url: https://github.com/bigcommerce/bigcommerce-api-python
rpm_license: MIT
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigcommerce-0.23.4-1.src.rpm
checksum_type: sha256
description:
  |Build Status| |Package Version|
  Wrapper over the ``requests`` library for communicating with the Bigcommerce v2 API.
  Install with ``pip install bigcommerce`` or ``easy_install bigcommerce``. Tested with
  python 3.7-3.9, and only requires ``requests`` and ``pyjwt``.


pkgId: 7fe66b9a730afb52140d3dab481544ee4013aa262ab435339843bdfeeb5d1c3f
name: python-bigdata-client
arch: src
version: 2.1.0
epoch: 0
release: 1
summary: please add a summary manually as the author left a blank one
url: https://pypi.org/project/bigdata-client/
rpm_license: None
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigdata-client-2.1.0-1.src.rpm
checksum_type: sha256
description:
  [![CI/CD Pipeline](https://github.com/RavenPack/bigdata-client/actions/workflows/cicd.yml/badge.svg)](https://github.com/RavenPack/bigdata-client/actions/workflows/cicd.yml)

  A Python client for accessing Bigdata API (https://bigdata.com).
  [Documentation (latest)](https://bigdata-python-client.readthedocs-hosted.com/en/latest/index.html)



  Tasks `poetry run task docs` or `poetry run task check-docs` produces an issue:
  ```
  Extension error:
  Could not import extension sphinxcontrib.spelling (exception: The 'enchant' C library was not found and maybe needs to be installed.
  ```
  In this situation it is necessary to set environment variable `PYENCHANT_LIBRARY_PATH=` with the pat to library
  ```bash
  export PYENCHANT_LIBRARY_PATH=$(brew --prefix enchant)/lib/libenchant-2.dylib
  poetry run task docs
  ```


pkgId: 0eb82bfca4ca1ce6a0e1c362365c420ac71c18fc9e5f0a5400b73504ac080f79
name: python-bigeye-airflow
arch: src
version: 0.1.34
epoch: 0
release: 1
url: https://docs.bigeye.com/docs
rpm_license: Proprietary
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigeye-airflow-0.1.34-1.src.rpm
checksum_type: sha256
summary:
  Bigeye Airflow Library supports Airflow versions >=2.2.0, <2.8.0 and offers custom operators for interacting with your your bigeye workspace.

description:
  The CreateMetricOperator creates metrics from a list of metric configurations provided to the operator.
  This operator will fill in reasonable defaults like setting thresholds.  It authenticates through an Airflow connection
  ID and offers the option to run the metrics after those metrics have been created.  Please review the link below to
  understand the structure of the configurations.

  [Create or Update Metric Swagger](https://docs.bigeye.com/reference/createmetric)


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id to which the metric configurations will be deployed.
  3. configuration: List[dict] - A list of metric configurations conforming to the following schema.
      ```
      schema_name: str
      table_name: str
      column_name: str
      metric_template_id: uuid.UUID
      metric_name: str
      description: str
      notifications: List[str]
      thresholds: List[dict]
      filters: List[str]
      group_by: List[str]
      user_defined_metric_name: str
      metric_type: SimpleMetricCategory
      default_check_frequency_hours: int
      update_schedule: str
      delay_at_update: str
      timezone: str
      should_backfill: bool
      lookback_type: str
      lookback_days: int
      window_size: str
      _window_size_seconds
      ```
  4. run_after_upsert: bool - If true it will run the metrics after creation.  Defaults to False.
  5. workspace_id: Optional[int] - The ID of the workspace where metrics should be created.
  If only 1 workspace configured, then will default to that else this will be required.



  The RunMetricsOperator will run metrics in Bigeye based on the following:

  1. All metrics for a given table, by providing warehouse ID, schema name and table name.
  2. All metrics for a given collection, by providing the collection ID.
  3. Any and all metrics, given a list of metric IDs.

  Currently, if a list of metric IDs is provided these will be run instead of metrics provided for
  warehouse_id, schema_name, table_name, and collection_id


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id for which metrics will be run.
  3. schema_name: str - The schema name for which metrics will be run.
  4. table_name: str - The table name for which metrics will be run.
  5. collection_id: int - The ID of the collection where the operator will run the metrics.
  6. metric_ids: List[int] - The metric ids to run.
  7. workspace_id: Optional[int] - The ID of the workspace where metrics should be run.
  If only 1 workspace configured, then will default to that else this will be required.
  8. circuit_breaker_mode: bool - Whether dag should raise an exception if metrics result in alerting state, default False.



  The CreateDeltaOperator creates deltas from a list of delta configurations provided to the operator.
  This operator will fill in reasonable defaults like column mappings.  It authenticates through an Airflow connection
  ID and offers the option to run the deltas after those deltas have been created.  Please review the link below to
  understand the structure of the configurations.


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id to which the metric configurations will be deployed.
  3. configuration: List[dict] - A list of delta configurations conforming to the following schema.
      ```
      delta_name: str
      fq_source_table_name: str
      target_table_comparisons: dict
      - example: {"target_table_comparisons": [{"fq_target_table_name": "Snowflake.TOOY_DEMO_DB.PROD_REPL.ORDERS"}]
      tolerance: Optional[float]
      - default = 0.0
      cron_schedule: Optional[dict]
      - default = None
      - example: {"cron_schedule": {"name": "Midnight UTC", "cron": "0 0 * * *"}}
      notification_channels: Optional[List[dict]]
      - default = None
      - example: {"notification_channels: [{"slack": "#data-alerts"}]
      ```
  4. run_after_upsert: bool - If true it will run the deltas after creation.  Defaults to False.
  5. workspace_id: Optional[int] - The ID of the workspace where deltas should be created.
  If only 1 workspace configured, then will default to that else this will be required.



  The RunDeltasOperator will run deltas in Bigeye based on the following:

  1. All deltas for a given table, by providing warehouse ID, schema name and table name.
  2. Any and all deltas, given a list of delta IDs.

  Currently, if a list of delta IDs is provided these will be run instead of metrics provided for
  warehouse_id, schema_name, table_name.


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id for which metrics will be run.
  3. schema_name: str - The schema name for which metrics will be run.
  4. table_name: str - The table name for which metrics will be run.
  5. delta_ids: List[int] - The delta ids to run.
  6. workspace_id: Optional[int] - The ID of the workspace where deltas should be run.
  If only 1 workspace configured, then will default to that else this will be required.
  7. circuit_breaker_mode: bool - Whether dag should raise an exception if deltas result in alerting state, default False.


pkgId: e922753c98517e554374320c4ec1212ee3ace649a3ee2f897819745a582da106
name: python-bigeye-airflow
arch: src
version: 0.1.35
epoch: 0
release: 1
url: https://docs.bigeye.com/docs
rpm_license: Proprietary
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigeye-airflow-0.1.35-1.src.rpm
checksum_type: sha256
summary:
  Bigeye Airflow Library supports Airflow versions >=2.2.0, <=2.9.3 and offers custom operators for interacting with your your bigeye workspace.

description:
  The CreateMetricOperator creates metrics from a list of metric configurations provided to the operator.
  This operator will fill in reasonable defaults like setting thresholds.  It authenticates through an Airflow connection
  ID and offers the option to run the metrics after those metrics have been created.  Please review the link below to
  understand the structure of the configurations.

  [Create or Update Metric Swagger](https://docs.bigeye.com/reference/createmetric)


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id to which the metric configurations will be deployed.
  3. configuration: List[dict] - A list of metric configurations conforming to the following schema.
      ```
      schema_name: str
      table_name: str
      column_name: str
      metric_template_id: uuid.UUID
      metric_name: str
      description: str
      notifications: List[str]
      thresholds: List[dict]
      filters: List[str]
      group_by: List[str]
      user_defined_metric_name: str
      metric_type: SimpleMetricCategory
      default_check_frequency_hours: int
      update_schedule: str
      delay_at_update: str
      timezone: str
      should_backfill: bool
      lookback_type: str
      lookback_days: int
      window_size: str
      _window_size_seconds
      ```
  4. run_after_upsert: bool - If true it will run the metrics after creation.  Defaults to False.
  5. workspace_id: Optional[int] - The ID of the workspace where metrics should be created.
  If only 1 workspace configured, then will default to that else this will be required.



  The RunMetricsOperator will run metrics in Bigeye based on the following:

  1. All metrics for a given table, by providing warehouse ID, schema name and table name.
  2. All metrics for a given collection, by providing the collection ID.
  3. Any and all metrics, given a list of metric IDs.

  Currently, if a list of metric IDs is provided these will be run instead of metrics provided for
  warehouse_id, schema_name, table_name, and collection_id


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id for which metrics will be run.
  3. schema_name: str - The schema name for which metrics will be run.
  4. table_name: str - The table name for which metrics will be run.
  5. collection_id: int - The ID of the collection where the operator will run the metrics.
  6. metric_ids: List[int] - The metric ids to run.
  7. workspace_id: Optional[int] - The ID of the workspace where metrics should be run.
  If only 1 workspace configured, then will default to that else this will be required.
  8. circuit_breaker_mode: bool - Whether dag should raise an exception if metrics result in alerting state, default False.



  The CreateDeltaOperator creates deltas from a list of delta configurations provided to the operator.
  This operator will fill in reasonable defaults like column mappings.  It authenticates through an Airflow connection
  ID and offers the option to run the deltas after those deltas have been created.  Please review the link below to
  understand the structure of the configurations.


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id to which the metric configurations will be deployed.
  3. configuration: List[dict] - A list of delta configurations conforming to the following schema.
      ```
      delta_name: str
      fq_source_table_name: str
      target_table_comparisons: dict
      - example: {"target_table_comparisons": [{"fq_target_table_name": "Snowflake.TOOY_DEMO_DB.PROD_REPL.ORDERS"}]
      tolerance: Optional[float]
      - default = 0.0
      cron_schedule: Optional[dict]
      - default = None
      - example: {"cron_schedule": {"name": "Midnight UTC", "cron": "0 0 * * *"}}
      notification_channels: Optional[List[dict]]
      - default = None
      - example: {"notification_channels: [{"slack": "#data-alerts"}]
      ```
  4. run_after_upsert: bool - If true it will run the deltas after creation.  Defaults to False.
  5. workspace_id: Optional[int] - The ID of the workspace where deltas should be created.
  If only 1 workspace configured, then will default to that else this will be required.



  The RunDeltasOperator will run deltas in Bigeye based on the following:

  1. All deltas for a given table, by providing warehouse ID, schema name and table name.
  2. Any and all deltas, given a list of delta IDs.

  Currently, if a list of delta IDs is provided these will be run instead of metrics provided for
  warehouse_id, schema_name, table_name.


  1. connection_id: str - The Airfow connection ID used to store the required Bigeye credential.
  2. warehouse_id: int - The Bigeye source/warehouse id for which metrics will be run.
  3. schema_name: str - The schema name for which metrics will be run.
  4. table_name: str - The table name for which metrics will be run.
  5. delta_ids: List[int] - The delta ids to run.
  6. workspace_id: Optional[int] - The ID of the workspace where deltas should be run.
  If only 1 workspace configured, then will default to that else this will be required.
  7. circuit_breaker_mode: bool - Whether dag should raise an exception if deltas result in alerting state, default False.


pkgId: e28f237f979eb5a0efc384818000ec1719729e9afff311d59387a1bc7e8f8e65
name: python-bigeye-cli
arch: src
version: 0.3.66
epoch: 0
release: 1
summary: Bigeye CLI offers developer tools for maintaining your developer workspace.
url: https://docs.bigeye.com/docs
rpm_license: Proprietary
rpm_group: Unspecified
rpm_buildhost: localhost
location_href: Packages/python-bigeye-cli-0.3.66-1.src.rpm
checksum_type: sha256
description:
  Version: 0.3.66


  **Setting up your local/build environment**
  Mac with Pyenv

  1. Install [brew](https://brew.sh/)
  2. Install [pyenv](https://formulae.brew.sh/formula/pyenv)
  3. Install python and define a default global:

  ```text
  pyenv install 3.9.13
  pyenv global 3.9.13
  ```

  Conda
  Install [conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html)
  Create environment with python3.9 or greater

  ```text
  conda create -n bigeye_env python=3.9
  conda activate bigeye_env
  ```

  Linux
  Most linux distributes include a default python distribution and we recommend using that default.

  **Installing Bigeye-CLI**

  You can install the Bigeye-CLI from your command line with the following command:

  ```text
  pip3 install bigeye-cli
  ```



  Before using the Bigeye CLI, you will need to configure your Bigeye credentials and workspaces. You can do this in the following ways:

  - Configuration Command
  - Environment Variables
  - Credentials and Configuration Files


  The quickest way to get started using the Bigeye CLI is to run the `bigeye configure` command. This command will walk you through the options to establish default credentials and configurations for the Bigeye CLI.


  ```console
  $ bigeye configure
  ```

  Using the bigeye configure command, there are two ways to authenticate: basic authentication and browser authentication.



  If you select basic authentication, the CLI will prompt you to enter your Bigeye username and password. This will create a default credential file at ~/.bigeye/credentials. This credential will be used for all CLI commands
  unless an environment variable is explicitly provided.



  Alternatively, you can use browser authentication (Linux/Mac OS only). Login to your bigeye workspace on a Chrome, Chromium or
  Firefox browser. Run **bigeye configure** and select browser_auth when prompted in the CLI and follow the
  instructions. If you use Chrome profiles, make sure to specify the profile email address you are logged into.

  Note: Your authentication will only be valid so long as your browser session is active. Not supported with SSO enabled.



  You can create an environment variable for your workspace credential and configuration files, this is helpful if you would like to store your credential and configuration files some place other than their default locations (~/.bigeye). You can do this by adding the following to your ~/.bashrc or ~/.zshrc file. Please review the format of these files in the below sections.

  ```shell
  export BIGEYE_API_CRED_FILE=/some/path/to/credentials.ini
  export BIGEYE_API_CONFIG_FILE=/some/path/to/config.ini
  ```



  Lastly, if you want to specify the credential per command, you can always pass a -b parameter with the file path for the desired credential. You can pass a -c parameter with the file path for the desired configuration.

  _Sample credentials file:_

  ```ini Text
  [DEFAULT]
  base_url = https://app.bigeye.com
  user = some_user@bigeye.com
  password = fakepassword1234
  ```

  _Sample configuration file:_

  ```ini
  [DEFAULT]
  workspace_id = 123
  ```



  The configuration files can help to simplify other CLI commands by defining common options in one file. See
  the table below for the complete list of configuration options.

  | **Key**                | **Description**                                                                                     | **Type**      | **Example**                            |
  | ---------------------- | --------------------------------------------------------------------------------------------------- | ------------- | -------------------------------------- |
  | workspace_id           | The id for the workspace [Required]                                                                 | Int           | 123                                    |
  | use_default_credential | Whether or not a unique credential is required for this workspace. Default: True                    | Bool          | True                                   |
  | bigconfig_input_path   | The path(s) to bigconfig files that are used for the given workspace.                               | List[strings] | path/bigconfig.yml,path2/bigconfig.yml |
  | bigconfig_output_path  | The path used to output bigconfig PLAN reports and FIXME files.                                     | String        | output/bigconfig_reports/              |
  | bigconfig_strict_mode  | API errors cause an exception if True. (Validation errors still cause an exception). Default: False | Bool          | False                                  |
  | bigconfig_auto_approve | Bigconfig applies should be allowed to run without excplit plan approvals. Default: False           | Bool          | False                                  |


  The configuration options above can be manually set directly in your config.ini files or you can utilize the below command to set the config values.

  ```console
  $ bigeye configure set <setting_key> <value> [OPTIONAL WORKSPACE]
  $ bigeye configure set bigconfig_input_path path/bigconfig.yml,path2/bigconfig -w data-science
  ```

  To view the current value of any specific configuration you can either open the config.ini file or utilize the below command.

  ```console
  $ bigeye configure get <setting_key> [OPTIONAL WORKSPACE]
  $ bigeye configure get bigconfig_input_path -w data-science
  ```



  The credential and configuration files are designed to allow Bigeye CLI users to easily switch between their Bigeye workspaces. Each file enforces the idea of a `DEFAULT` section. All subsequent CLI commands will inherit the properties defined in the `DEFAULT`section unless they are directed otherwise. Every command in the Bigeye CLI has the ability to provide a `--workspace` (`-w`) option to override the use of the default workspace.

  To setup a non-default workspace, run the following command:

  ```console
  $ bigeye configure -w data-science
  ```

  Or, open up the files and manually edit them based on your setup. Here are some samples.

  _Sample configuration file with multiple workspaces:_

  ```ini
  [DEFAULT]
  workspace_id = 123
  bigconfig_input_path = absolute_path/bigconfig.yml,absolute_path2/bigconfig.yml
  bigconfig_output_path = absolute_path/output/

  [workspace data-science]
  workspace_id = 234
  use_default_credential = True
  bigconfig_input_path = absolute_path/ds/bigconfig.yml,absolute_path2/ds/bigconfig.yml

  [workspace dev]
  workspace_id = 100
  use_default_credential = False
  bigconfig_auto_approve = True
  bigconfig_input_path = absolute_path/bigconfig.yml,absolute_path2/bigconfig.yml
  ```

  _Sample credentials file with multiple workspaces:_

  ```ini
  [DEFAULT]
  base_url = https://app.bigeye.com
  user = some_user@bigeye.com
  password = fakepassword1234

  [dev]
  base_url = https://app.bigeye.com
  user = some_user@bigeye.com
  password = fakepassword1234
  ```

  **Note:** Most use cases will only require you to have a single `DEFAULT` credential that all workspaces can utilize. Running the `bigeye configure -w <workspace-name>` command for a non-default workspace will prompt you to inherit the credentials from default or create a new set of credentials. A workspace credential is only required if the `use_default_credential` setting is set to False in the configuration of the corresponding workspace.



  Configuration Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye configure [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `get`: Get any specific configuration settings.
  * `list`: List all configured workspaces.
  * `set`: Set any specific configuration settings.



  Get any specific configuration settings.

  **Usage**:

  ```console
  $ bigeye configure get [OPTIONS] [SETTING_KEY]
  ```

  **Arguments**:

  * `[SETTING_KEY]`: The name of the configuration setting.

  **Options**:

  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--help`: Show this message and exit.



  List all configured workspaces.

  **Usage**:

  ```console
  $ bigeye configure list [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `--help`: Show this message and exit.



  Set any specific configuration settings.

  **Usage**:

  ```console
  $ bigeye configure set [OPTIONS] [SETTING_KEY] [SETTING_VALUE]
  ```

  **Arguments**:

  * `[SETTING_KEY]`: The name of the configuration setting.
  * `[SETTING_VALUE]`: The value of the configuration setting.

  **Options**:

  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--help`: Show this message and exit.



  Workspace Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye workspace [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `create-named-schedule`: Create a named, cron based schedule
  * `delete-group`: Delete a group.
  * `delete-named-schedule`: Delete a named schedule.
  * `delete-user`: Deletes a users account.
  * `delete-workspace`: Delete a workspace.
  * `edit-group-access`: Edit the workspaces that a group can access.
  * `edit-user`: Edit an existing user's access to groups.
  * `export-named-schedule`: Export cron schedule to yaml file
  * `invite-user`: Invite a user to a workspace.
  * `schedule-all-metrics`: Schedule all metrics in a workspace.
  * `unschedule-all-metrics`: Unschedule all metrics in a workspace.
  * `upsert-group`: Create or update a group.
  * `upsert-named-schedule`: Create a named, cron based schedule.
  * `upsert-workspace`: Create or update a workspace.



  Create a named, cron based schedule

  **Usage**:

  ```console
  $ bigeye workspace create-named-schedule [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --name TEXT`: The user defined name of the schedule  [required]
  * `-c, --cron TEXT`: The cron string to define the schedule  [required]
  * `--help`: Show this message and exit.



  Delete a group.

  **Usage**:

  ```console
  $ bigeye workspace delete-group [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --group_name TEXT`: Group name.  [required]
  * `--help`: Show this message and exit.



  Delete a named schedule.

  **Usage**:

  ```console
  $ bigeye workspace delete-named-schedule [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --name TEXT`: The user defined name of the schedule  [required]
  * `--help`: Show this message and exit.



  Deletes a users account.

  **Usage**:

  ```console
  $ bigeye workspace delete-user [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --user_name TEXT`: User name.
  * `-email, --user_email TEXT`: User email.
  * `--help`: Show this message and exit.



  Delete a workspace.

  **Usage**:

  ```console
  $ bigeye workspace delete-workspace [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --workspace_name TEXT`: Workspace name.  [required]
  * `--help`: Show this message and exit.



  Edit the workspaces that a group can access.

  **Usage**:

  ```console
  $ bigeye workspace edit-group-access [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --group-name TEXT`: Group name.  [required]
  * `-id, --group-id INTEGER`: Group ID.
  * `-wn, --workspace-name TEXT`: List of workspace names to edit access. E.g. -wn workspace1 -wn workspace2  [required]
  * `-access, --access-level TEXT`: The access this group should have to the workspaces provided. Must be either `Edit` or `Manage`  [default: Edit]
  * `-op, --operation INTEGER`: Operation type.
   ROLE_OPERATION_UNSPECIFIED:0
  ROLE_OPERATION_GRANT:1
  ROLE_OPERATION_REVOKE:2  [default: 1]
  * `--help`: Show this message and exit.



  Edit an existing user's access to groups.

  **Usage**:

  ```console
  $ bigeye workspace edit-user [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --user_name TEXT`: User name.
  * `-email, --user_email TEXT`: User email.
  * `-group, --group_name TEXT`: The names of the groups that should be edited for this users access. E.g. -group team1 -group team2  [required]
  * `-op, --operation INTEGER`: Operation type.
   GROUP_USER_OPERATION_UNSPECIFIED:0
  GROUP_USER_OPERATION_ADD:1
  GROUP_USER_OPERATION_REMOVE:2  [default: 1]
  * `--help`: Show this message and exit.



  Export cron schedule to yaml file

  **Usage**:

  ```console
  $ bigeye workspace export-named-schedule [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --name TEXT`: The name of the schedule(s) to export. None means all
  * `-op, --output_path TEXT`: Path to output files. None means current directory
  * `--help`: Show this message and exit.



  Invite a user to a workspace.

  **Usage**:

  ```console
  $ bigeye workspace invite-user [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --user_name TEXT`: User name.  [required]
  * `-email, --user_email TEXT`: Email where invite will be sent.  [required]
  * `-group, --group_name TEXT`: The names of the groups that the user should belong to. E.g. -group team1 -group team2
  * `--help`: Show this message and exit.



  Schedule all metrics in a workspace.

  **Usage**:

  ```console
  $ bigeye workspace schedule-all-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-type, --time_interval_type INTEGER`: Time interval type.
   UNDEFINED_TIME_INTERVAL_TYPE:0
  HOURS_TIME_INTERVAL_TYPE:1
  MINUTES_TIME_INTERVAL_TYPE:2
  SECONDS_TIME_INTERVAL_TYPE:3
  DAYS_TIME_INTERVAL_TYPE:4
  WEEKDAYS_TIME_INTERVAL_TYPE:5
  MARKET_DAYS_TIME_INTERVAL_TYPE:6
  MONTHS_TIME_INTERVAL_TYPE:7  [default: 1]
  * `-value, --interval_value INTEGER`: Number of intervals to set on all metric schedules.  If 0 use unschedule all metrics.  [required]
  * `--help`: Show this message and exit.



  Unschedule all metrics in a workspace.

  **Usage**:

  ```console
  $ bigeye workspace unschedule-all-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--help`: Show this message and exit.



  Create or update a group.

  **Usage**:

  ```console
  $ bigeye workspace upsert-group [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --group_name TEXT`: Group name.  [required]
  * `-id, --group_id INTEGER`: Group ID.
  * `--help`: Show this message and exit.



  Create a named, cron based schedule. Files have priority over arguments

  **Usage**:

  ```console
  $ bigeye workspace upsert-named-schedule [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --name TEXT`: The user defined name of the schedule
  * `-c, --cron TEXT`: The cron string to define the schedule
  * `-sf, --schedule_file TEXT`: A yaml file containing a schedule definition.
  * `--help`: Show this message and exit.



  Create or update a workspace.

  **Usage**:

  ```console
  $ bigeye workspace upsert-workspace [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-name, --workspace_name TEXT`: Workspace name.  [required]
  * `-id, --workspace_id INTEGER`: Workspace ID.
  * `--help`: Show this message and exit.



  Bigconfig Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye bigconfig [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `apply`: Applies a purge of deployed metrics or...
  * `export`: Exports existing metrics into a valid...
  * `plan`: Executes a plan for purging sources or...



  Applies a purge of deployed metrics or applies Bigconfig files from the input path/current working directory to
  the Bigeye workspace.

  **Usage**:

  ```console
  $ bigeye bigconfig apply [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-ip, --input_path TEXT`: List of paths containing Bigconfig files or pointing to a Bigconfig file. E.g. -ip path1 -ip path2. If no input path is defined then current working directory will be used.
  * `-op, --output_path TEXT`: Output path where reports and fixme files will be saved. If no output path is defined then current working directory will be used.
  * `-psn, --purge_source_name TEXT`: List of source names to purge  E.g. -psn source_1 -psn source_2.
  * `-purge_all, --purge_all_sources`: Purge all sources: True or False.
  * `-nq, --no_queue`: Don't submit to queue: True or False.
  * `-r, --recursive`: Search all input directories recursively.
  * `-strict, --strict_mode`: API errors cause an exception if True. (Validation errors still cause an exception)
  * `-auto_approve, --auto_approve`: Explicit plan approval is not needed prior to execution.
  * `--help`: Show this message and exit.



  Exports existing metrics into a valid Bigconfig file.

  **Usage**:

  ```console
  $ bigeye bigconfig export [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-op, --output_path TEXT`: Output path where reports and fixme files will be saved. If no output path is defined then current working directory will be used.
  * `-cid, --collection_id INTEGER`: Collection ID of metrics to be exported.
  * `-tid, --table_id INTEGER`: Table ID of metrics to be exported.
  * `-mid, --metric_id INTEGER`: Metric ID of metrics to be exported.
  * `--help`: Show this message and exit.



  Executes a plan for purging sources or processing bigconfig files in the input path/current
  working directory.

  **Usage**:

  ```console
  $ bigeye bigconfig plan [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-ip, --input_path TEXT`: List of paths containing Bigconfig files or pointing to a Bigconfig file. E.g. -ip path1 -ip path2. If no input path is defined then current working directory will be used.
  * `-op, --output_path TEXT`: Output path where reports and fixme files will be saved. If no output path is defined then current working directory will be used.
  * `-psn, --purge_source_name TEXT`: List of source names to purge  E.g. -psn source_1 -psn source_2.
  * `-purge_all, --purge_all_sources`: Purge all sources: True or False.
  * `-nq, --no_queue`: Don't submit to queue: True or False.
  * `-r, --recursive`: Search all input directories recursively.
  * `-strict, --strict_mode`: API errors cause an exception if True. (Validation errors still cause an exception)
  * `-cicd, --cicd_report`: Add the report to a pull request. (Github only)
  * `--help`: Show this message and exit.



  Catalog Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye catalog [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `add-source`: Adds a source to specified Bigeye workspace.
  * `backfill-autothresholds`: Backfills autothresholds by warehouse id,...
  * `backfill-metrics`: Backfills metrics by warehouse id, schema...
  * `delete-metrics`: Delete metrics in a warehouse id, by...
  * `delete-source`: Delete a source from specified Bigeye...
  * `delete-template`: Delete a template.
  * `delete-virtual-table`: Delete a virtual table.
  * `export-metric-template`: Export metric templates to yaml files
  * `export-virtual-table`: Export virtual table to yaml file
  * `get-all-metric-templates`: Retrieve all metric templates and output...
  * `get-metric-info`: Outputs metric info to a file.
  * `get-metric-queries`: Gets the debug queries for all metrics by...
  * `get-table-info`: Outputs table info to a file for an entire...
  * `ingest-dbt-core-run-info`: Ingest manifest.json and run_results.json...
  * `ingest-owners-from-dbt`: Ingest table owners from dbt manifest.json...
  * `rebuild`: Rebuilds/Reprofiles a source by warehouse...
  * `regen-autometrics`: Regenerates Autometrics by warehouse id OR...
  * `run-metrics`: Runs metrics by warehouse id, schema...
  * `schedule-all-metrics`: Updates schedule for all metrics in a...
  * `set-metric-time`: Sets metric times from a list of possible...
  * `track-schema-changes`: Track schema changes for sources on demand...
  * `unschedule-all-metrics`: Unschedule all metrics by warehouse,...
  * `unset-metric-time`: Unsets metric times for whole warehouse or...
  * `upsert-template`: Create or update a template for a source.
  * `upsert-virtual-table`: Create or update a virtual table.



  Adds a source to specified Bigeye workspace.  Supports either source configs stored in AWS Secrets manager OR
  locally in file.

  **Usage**:

  ```console
  $ bigeye catalog add-source [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --secret_name TEXT`: The name of the secret to retrieve from AWS Secrets Manager
  * `-srn, --secret_region_name TEXT`: AWS Secret Manager Region Name  [default: us-west-2]
  * `-sccf, --source_catalog_config_file TEXT`: The file containing the necessary parameters for adding a source to Bigeye.
  * `--help`: Show this message and exit.



  Backfills autothresholds by warehouse id, schema names, and/or table ids.

  **Usage**:

  ```console
  $ bigeye catalog backfill-autothresholds [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `--help`: Show this message and exit.



  Backfills metrics by warehouse id, schema names, and/or table ids.

  **Usage**:

  ```console
  $ bigeye catalog backfill-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `--delete_history`: Delete metric run history
  * `-q, --queue`: Submit the batch to a queue. (Recommended for larger batches)
  * `--help`: Show this message and exit.



  Delete metrics in a warehouse id, by schema names, or by table_ids.  Also, can filter by multipe
  metric types.

  **Usage**:

  ```console
  $ bigeye catalog delete-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `-m, --metric_type TEXT`: Delete by name of the metric type.UNDEFINED_PREDEFINED_METRIC_NAME, PERCENT_NULL, COUNT_NULL, PERCENT_EMPTY_STRING, COUNT_EMPTY_STRING, PERCENT_UNIQUE, PERCENT_VALUE_IN_LIST, AVERAGE, MIN, MAX, SUM, COUNT_ROWS, COUNT_DISTINCT, HOURS_SINCE_MAX_DATE, HOURS_SINCE_MAX_TIMESTAMP, COUNT_TRUE, PERCENT_TRUE, COUNT_FALSE, PERCENT_FALSE, COUNT_USA_PHONE, PERCENT_USA_PHONE, COUNT_USA_ZIP_CODE, PERCENT_USA_ZIP_CODE, PERCENT_UUID, COUNT_TIMESTAMP_STRING, PERCENT_TIMESTAMP_STRING, COUNT_DUPLICATES, COUNT_USA_STATE_CODE, PERCENT_USA_STATE_CODE, VARIANCE, SKEW, KURTOSIS, GEOMETRIC_MEAN, HARMONIC_MEAN, COUNT_UUID, COUNT_CUSIP, PERCENT_CUSIP, COUNT_SEDOL, PERCENT_SEDOL, COUNT_ISIN, PERCENT_ISIN, COUNT_LEI, PERCENT_LEI, COUNT_FIGI, PERCENT_FIGI, COUNT_PERM_ID, PERCENT_PERM_ID, COUNT_NAN, PERCENT_NAN, COUNT_LONGITUDE, PERCENT_LONGITUDE, COUNT_LATITUDE, PERCENT_LATITUDE, COUNT_NOT_IN_FUTURE, PERCENT_NOT_IN_FUTURE, COUNT_DATE_NOT_IN_FUTURE, PERCENT_DATE_NOT_IN_FUTURE, MEDIAN, PERCENTILE, COUNT_NOT_NULL, STRING_LENGTH_AVERAGE, STRING_LENGTH_MIN, STRING_LENGTH_MAX, COUNT_SSN, PERCENT_SSN, COUNT_EMAIL, PERCENT_EMAIL, ROWS_INSERTED, HOURS_SINCE_LAST_LOAD, COUNT_READ_QUERIES, PERCENT_NOT_NULL, FRESHNESS, VOLUME, FRESHNESS_DATA, VOLUME_DATA, COUNT_VALUE_IN_LIST
  * `--help`: Show this message and exit.



  Delete a source from specified Bigeye workspace.

  **Usage**:

  ```console
  $ bigeye catalog delete-source [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: The ID of the warehouse to delete.
  * `--help`: Show this message and exit.



  Delete a template.

  **Usage**:

  ```console
  $ bigeye catalog delete-template [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-tid, --template_id INTEGER`: The ID of the metric template  [required]
  * `--help`: Show this message and exit.



  Delete a virtual table.

  **Usage**:

  ```console
  $ bigeye catalog delete-virtual-table [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-vtn, --table_name TEXT`: The name of the virtual table  [required]
  * `-sn, --source_name TEXT`: The name of the source that contains the virtual table  [required]
  * `--help`: Show this message and exit.



  Export metric templates to yaml files

  **Usage**:

  ```console
  $ bigeye catalog export-metric-template [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-tn, --name TEXT`: The name of the template(s) to export. None means all
  * `-op, --output_path TEXT`: Path to output files. None means current directory
  * `--help`: Show this message and exit.



  Export virtual table to yaml file

  **Usage**:

  ```console
  $ bigeye catalog export-virtual-table [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-vtn, --name TEXT`: The name of the virtual table(s) to export. None means all
  * `-op, --output_path TEXT`: Path to output files. None means current directory
  * `--help`: Show this message and exit.



  Retrieve all metric templates and output to a file.

  **Usage**:

  ```console
  $ bigeye catalog get-all-metric-templates [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-ps, --page_size INTEGER`: How many results to return per page  [default: 0]
  * `-s, --search TEXT`: A search string to narrow results
  * `-op, --output_path TEXT`: The path to output templates
  * `-fn, --file_name TEXT`: User defined file name
  * `--help`: Show this message and exit.



  Outputs metric info to a file.  Includes metric configuration and details about recent runs.

  **Usage**:

  ```console
  $ bigeye catalog get-metric-info [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs. E.g. -tid 123 -tid 124or schema names.
  * `-ms, --metric_status [HEALTHY|ALERTING|FAILED|UNKNOWN]`: Used to query metric of particular status.
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--conf_only`: Output only the metric configuration.
  * `--help`: Show this message and exit.



  Gets the debug queries for all metrics by warehouse id, schema names, or table ids.

  **Usage**:

  ```console
  $ bigeye catalog get-metric-queries [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Outputs table info to a file for an entire warehouse, certain schemas, or certain tables.

  **Usage**:

  ```console
  $ bigeye catalog get-table-info [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs. E.g. -tid 123 -tid 124
  * `-tn, --table_name TEXT`: Table Namess. E.g. -tn some_table -tn some_other_table
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Ingest manifest.json and run_results.json from different dbt commands.

  **Usage**:

  ```console
  $ bigeye catalog ingest-dbt-core-run-info [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-tp, --target_path TEXT`: The path to the target directory created with dbt run, build, and test commands.  [default: ./target]
  * `-pn, --project_name TEXT`: The name of the dbt project. Will attempt to parse from metadata section of manifest.json, if not present
  * `-jn, --job_name TEXT`: The name of the dbt job.
  * `-jid, --job_run_id TEXT`: The ID of the dbt job run.
  * `-purl, --project_url TEXT`: The URL of the project.
  * `-jurl, --job_url TEXT`: The URL of the job.
  * `-jrurl, --job_run_url TEXT`: The URL of the job run.
  * `--help`: Show this message and exit.



  Ingest table owners from dbt manifest.json file.

  **Usage**:

  ```console
  $ bigeye catalog ingest-owners-from-dbt [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-mf, --manifest_file TEXT`: The path to the dbt manifest file.
  * `-sn, --source_name TEXT`: The name of the source in Bigeye where owners will be ingested.
  * `-sid, --source_id INTEGER`: The id of the source in Bigeye where owners will be ingested.
  * `--help`: Show this message and exit.



  Rebuilds/Reprofiles a source by warehouse id or a schema by warehouse id and schema name.

  **Usage**:

  ```console
  $ bigeye catalog rebuild [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID  [required]
  * `-sn, --schema_name TEXT`: Schema Name
  * `--help`: Show this message and exit.



  Regenerates Autometrics by warehouse id OR warehouse id and list of schema names OR list of table ids.

  **Usage**:

  ```console
  $ bigeye catalog regen-autometrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID.  If none will look for Table IDs.  If value then will pull all table ids for warehouse
  * `-sn, --schema_name TEXT`: List of Schema Names  E.g. -sn schema_1 -sn schema_2.  Do not include warehouse name -- GREENE_HOMES_DEMO_STANDARD.CONFORMED is fully qualified and CONFORMED is the schema name.
  * `-tid, --table_id INTEGER`: List of Table IDs.  E.g. -tid 123 -tid 124
  * `--help`: Show this message and exit.



  Runs metrics by warehouse id, schema names, and/or table ids

  **Usage**:

  ```console
  $ bigeye catalog run-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Names.  E.g. -sn schema_1 -sn schema_2.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `-q, --queue`: Submit the batch to a queue. (Recommended for larger batches)
  * `--help`: Show this message and exit.



  Updates schedule for all metrics in a warehouse.

  **Usage**:

  ```console
  $ bigeye catalog schedule-all-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-type, --time_interval_type INTEGER`: Time interval type.
   UNDEFINED_TIME_INTERVAL_TYPE:0
  HOURS_TIME_INTERVAL_TYPE:1
  MINUTES_TIME_INTERVAL_TYPE:2
  SECONDS_TIME_INTERVAL_TYPE:3
  DAYS_TIME_INTERVAL_TYPE:4
  WEEKDAYS_TIME_INTERVAL_TYPE:5
  MARKET_DAYS_TIME_INTERVAL_TYPE:6
  MONTHS_TIME_INTERVAL_TYPE:7  [default: 1]
  * `-value, --interval_value INTEGER`: Number of intervals to set on all metric schedules.  If 0 use unschedule all metrics.  [required]
  * `--help`: Show this message and exit.



  Sets metric times from a list of possible metric column names.  Can set for whole warehouse or for a list of
  table IDs.

  **Usage**:

  ```console
  $ bigeye catalog set-metric-time [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sid, --schema_id INTEGER`: Schema ID
  * `-tid, --table_id INTEGER`: List of table IDs.
  * `-cn, --metric_column_name TEXT`: Possible metric column names.  [required]
  * `-r, --replace`: replace metric times if already present on tables?  Default is false.
  * `--help`: Show this message and exit.



  Track schema changes for sources on demand or every n hour(s) and notify specified channels.
  Environment variables SMTP_USER and SMTP_PASSWORD MUST be set if using email notification.

  **Usage**:

  ```console
  $ bigeye catalog track-schema-changes [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-src, --source TEXT`: A list of source names to track. (None will track changes from all sources)
  * `-s, --since TEXT`: Track schema changes from this date. (Formatted as yyyy-MM-dd)  [default: 1970-01-01]
  * `-f, --frequency INTEGER`: Every n hours to check for schema changes. (None will only run the check once)  [default: 0]
  * `-ct, --change_type [CREATED|DELETED|TYPE_CHANGED]`: The types of changes to track; i.e additions, changes, deletions. (None will track all types)  [default: DELETED, CREATED, TYPE_CHANGED]
  * `-nc, --channel TEXT`: A list of notification channels; e.g. -nc email@mydomain.com -nc #slack_channel -nc https://www.webhook.com
  * `-smtp, --server TEXT`: The server to send emails.
  * `-p, --port INTEGER`: The server port to send emails.  [default: 587]
  * `-t, --token TEXT`: The token to utilize the Slack web client. (Will attempt to use environment variable SLACK_BOT_TOKEN, if not set)
  * `--help`: Show this message and exit.



  Unschedule all metrics by warehouse, schema or tables.

  **Usage**:

  ```console
  $ bigeye catalog unschedule-all-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-sn, --schema_name TEXT`: List of Schema Name.  E.g. -sn schema_1.
  * `-tid, --table_id INTEGER`: Table IDs.  E.g. -tid 123 -tid 124
  * `--help`: Show this message and exit.



  Unsets metric times for whole warehouse or for a list og table IDs.

  **Usage**:

  ```console
  $ bigeye catalog unset-metric-time [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse ID
  * `-tid, --table_id INTEGER`: List of table IDs.
  * `--help`: Show this message and exit.



  Create or update a template for a source. Files have priority over arguments

  **Usage**:

  ```console
  $ bigeye catalog upsert-template [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-sn, --source_name TEXT`: The name of the source where the template will be defined
  * `-tn, --name TEXT`: The user defined name of the template
  * `-tb, --body TEXT`: The string to define the template
  * `-rt, --returns [BOOLEAN|NUMERIC]`: The data type returned by the template; i.e. NUMERIC, BOOLEAN
  * `-p, --params TEXT`: A list of key/value pairs for parameters; ex. -p column=COLUMN_REFERENCE -p table=STRING
  * `-tf, --template_file TEXT`: A file containing a template definition.
  * `--help`: Show this message and exit.



  Create or update a virtual table. Files have priority over arguments

  **Usage**:

  ```console
  $ bigeye catalog upsert-virtual-table [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-vtn, --table_name TEXT`: The name of the virtual table
  * `-s, --sql TEXT`: The SQL to define the table
  * `-sn, --source_name TEXT`: The name of the source where the virtual table will be defined
  * `-u, --update`: Create if false. Update if true.
  * `-tf, --table_file TEXT`: A yaml file containing a template definition.
  * `--help`: Show this message and exit.



  Metric Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye metric [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `dbt-tests-to-metrics`: Convert tests in a dbt schema.yml to...
  * `get-info`: Outputs metric info to a file.
  * `get-metric-queries`: Gets the debug queries for all metrics by...
  * `run`: Run metric by id(s)
  * `upsert`: Upsert single metric from file.
  * `upsert-from-path`: Upsert multiple metrics from files stored...



  Convert tests in a dbt schema.yml to metrics in Bigeye

  **Usage**:

  ```console
  $ bigeye metric dbt-tests-to-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wn, --warehouse_name TEXT`: The name of the source to deploy metrics  [required]
  * `-sn, --schema_name TEXT`: The name of the schema to deploy metrics  [required]
  * `-sf, --schema_file TEXT`: The path to the dbt schema file  [required]
  * `-auto, --use_auto`: Use autothresholds over default constant
  * `-bigconfig, --to_bigconfig`: Convert the dbt tests to bigconfig.
  * `-op, --output_path TEXT`: Output path where bigconfig file will be saved. If no output path is defined then current working directory will be used. Requires --to_bigconfig to be true.  [default: /home/runner/work/semantic-data-platform/semantic-data-platform/bigeye-integrations/bigeye-cli]
  * `--help`: Show this message and exit.



  Outputs metric info to a file.  Includes metric configuration and details about recent runs.

  **Usage**:

  ```console
  $ bigeye metric get-info [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-mid, --metric_id INTEGER`: Metric Ids.  [required]
  * `-ms, --metric_status [HEALTHY|ALERTING|FAILED|UNKNOWN]`: Used to query metric of particular status.
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--conf_only`: Output only the metric configuration.
  * `--help`: Show this message and exit.



  Gets the debug queries for all metrics by warehouse id, schema names, or table ids.

  **Usage**:

  ```console
  $ bigeye metric get-metric-queries [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-mid, --metric_id INTEGER`: Metric Ids.  [required]
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Run metric by id(s)

  **Usage**:

  ```console
  $ bigeye metric run [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-mid, --metric_id INTEGER`: Metric Ids.  [required]
  * `-q, --queue`: Submit the batch to a queue. (Recommended for larger batches)
  * `--help`: Show this message and exit.



  Upsert single metric from file.

  **Usage**:

  ```console
  $ bigeye metric upsert [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-f, --file TEXT`: File containing SimpleUpsedrtMetricRequest or MetricConfiguration  [required]
  * `-t, --file_type [SIMPLE|FULL]`: Metric File Type.  Simple conforms to SimpleUpsertMetricRequest and Full conforms to MetricConfiguration  [required]
  * `-wid, --warehouse_id INTEGER`: (Optional) Warehouse ID.  If specified it will reduce the text based search for the table.warehouse
  * `-mid, --metric_id INTEGER`: (Optional) Metric Id.  If specified it will reduce the text based search for existing metric.
  * `--help`: Show this message and exit.



  Upsert multiple metrics from files stored in path.

  **Usage**:

  ```console
  $ bigeye metric upsert-from-path [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-twid, --target_warehouse_id INTEGER`: Deploy Metrics to target Warehouse ID.  [required]
  * `-sp, --source_path TEXT`: Source path file containing the metrics to migrate.  [required]
  * `-srct, --set_row_creation_time`: Set the row creation time in target warehouse for tables found in source path.
  * `-uc, --update_collections`: Update existing collections to include new metrics from target warehouse.
  * `--help`: Show this message and exit.



  Issues Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye issues [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `get-issues`: Gets issues and writes info to files.
  * `update-issue`: Updates an issue in Bigeye and returns the...



  Gets issues and writes info to files.

  **Usage**:

  ```console
  $ bigeye issues get-issues [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-wid, --warehouse_id INTEGER`: Warehouse IDs.
  * `-sn, --schema_name TEXT`: Schema names
  * `-mid, --metric_id INTEGER`: Metric IDs.
  * `-cid, --collection_id INTEGER`: Collection IDs
  * `-iid, --issue_id INTEGER`: Issue IDs
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Updates an issue in Bigeye and returns the Issue object from the protobuff.

  **Usage**:

  ```console
  $ bigeye issues update-issue [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-iid, --issue_id INTEGER`: Issue ID  [required]
  * `-status, --issue_status TEXT`: The status update. Options are ACKNOWLEDGED or CLOSED.
  * `-by, --updated_by TEXT`: The user providing the update.
  * `-m, --message TEXT`: The message to attach to the issue.
  * `-cl, --closing_label TEXT`: Used to train Bigeye when closing an issue. Options are TRUE_POSITIVE, FALSE_POSITIVE, EXPECTED.
  * `--help`: Show this message and exit.



  Deltas Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye deltas [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `cicd`: Creates a delta based on...
  * `create-delta`: Creates deltas between tables from a...
  * `create-template`: Create a template delta configuration file...
  * `run-delta`: Runs a delta by Delta ID.
  * `suggest-deltas`: Suggests and creates Deltas with default...



  Creates a delta based on SimpleDeltaConfiguration and integrates the results with the provided VCS vendor.

  **Usage**:

  ```console
  $ bigeye deltas cicd [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-dcc, --delta_cicd_config TEXT`: The yaml file containing the parameters for the DeltaCICDConfig class  [required]
  * `--help`: Show this message and exit.



  Creates deltas between tables from a Simple Delta configuration file that contains multiple delta configurations.
  Enforces 1:1 column comparisons by case-insensitive column names if no column mappings are declared in
  configuration.

  **Usage**:

  ```console
  $ bigeye deltas create-delta [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-dc, --delta_conf TEXT`: Simple Delta configuration file.  [required]
  * `-ul, --update_lineage`: Should lineage between source and target be checked/created if doesn't exist.
  * `--help`: Show this message and exit.



  Create a template delta configuration file based on table names.

  **Usage**:

  ```console
  $ bigeye deltas create-template [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-stn, --source_table_name TEXT`: The fully qualified name of the source table  [required]
  * `-ttn, --target_table_name TEXT`: The fully qualified name of the target table  [required]
  * `-op, --output_path TEXT`: Path to write the template.  [required]
  * `--help`: Show this message and exit.



  Runs a delta by Delta ID.

  **Usage**:

  ```console
  $ bigeye deltas run-delta [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-did, --delta_id INTEGER`: Id of delta.  [required]
  * `-ar, --await_results`: Should command wait for delta run to complete, default False.
  * `--help`: Show this message and exit.



  Suggests and creates Deltas with default behavior and outputs all Simple Delta Configurations to a file.

  **Usage**:

  ```console
  $ bigeye deltas suggest-deltas [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-source, --source_selector TEXT`: The pattern of tables in the source to select. Wildcard (*) indicates all tables or all schemas, e.g. source_1.*.* would be all schemas in source_1.  [required]
  * `-target, --target_selector TEXT`: The pattern of tables in the target to select. Wildcard (*) indicates all tables or all schemas, e.g. source_2.*.* would be all schemas in source_2.  [required]
  * `-mt, --match_type [strict|fuzzy]`: How to match tables between the source and target destinations. Strict will only create relations if table names match exactly, Fuzzy will attempt to create relations using a fuzzy match.  [default: strict]
  * `-op, --output_path TEXT`: File to write the delta configuration.  [required]
  * `--help`: Show this message and exit.



  Collections Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye collections [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `add-metric`: Add metric to a Collection.
  * `backfill-metrics`: Backfill all metrics in a Collection.
  * `get-metric-info`: Get metric info for all metrics in...
  * `get-metric-queries`: Gets the debug queries for all metrics by...
  * `migrate-from-json`: Loads metrics from Collection oriented...
  * `run-metrics`: Run all metrics in a Collection.



  Add metric to a Collection.

  **Usage**:

  ```console
  $ bigeye collections add-metric [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-mid, --metric_id INTEGER`: Metric ID  [required]
  * `-cid, --collection_id INTEGER`: Collection ID  [required]
  * `--help`: Show this message and exit.



  Backfill all metrics in a Collection.

  **Usage**:

  ```console
  $ bigeye collections backfill-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--from_collections`: Scrapes all Collections in customer workspace for Metric Info.
  * `--collection_ids INTEGER`: Collection IDs.  Scrape certain Collections for Metric Info.
  * `--delete_history`: Delete metric run history
  * `-q, --queue`: Submit the batch to a queue. (Recommended for larger batches)
  * `--help`: Show this message and exit.



  Get metric info for all metrics in Collection.

  **Usage**:

  ```console
  $ bigeye collections get-metric-info [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `--from_collections`: Scrapes all Collections in customer workspace for Metric Info.
  * `--collection_ids INTEGER`: Collection IDs.  Scrape certain Collections for Metric Info.
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Gets the debug queries for all metrics by warehouse id, schema names, or table ids.

  **Usage**:

  ```console
  $ bigeye collections get-metric-queries [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-cid, --collection_id INTEGER`: Collection ID  [required]
  * `-op, --output_path TEXT`: File to write the failed metric configurations to.  [required]
  * `--help`: Show this message and exit.



  Loads metrics from Collection oriented metric info output.  Used to migrate metrics from one warehouse to
  another, identical, warehouse

  **Usage**:

  ```console
  $ bigeye collections migrate-from-json [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-twid, --target_warehouse_id INTEGER`: Deploy Metrics to target Warehouse ID.  [required]
  * `-ip, --input_path TEXT`: Path to read from.  [required]
  * `-kn, --keep_notifications`: Keep Notifications from versioned or templated metric configuration.
  * `-kid, --keep_ids`: Keep Metric and Collection IDs from versioned or templated metric configuration.  If kept this would update existing metrics and collections.  If not kept it would create new.
  * `--help`: Show this message and exit.



  Run all metrics in a Collection.

  **Usage**:

  ```console
  $ bigeye collections run-metrics [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-cid, --collection_id INTEGER`: Collection ID  [required]
  * `-q, --queue`: Submit the batch to a queue. (Recommended for larger batches)
  * `--help`: Show this message and exit.



  Lineage Commands for Bigeye CLI

  **Usage**:

  ```console
  $ bigeye lineage [OPTIONS] COMMAND [ARGS]...
  ```

  **Options**:

  * `--install-completion`: Install completion for the current shell.
  * `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
  * `--help`: Show this message and exit.

  **Commands**:

  * `create-node`: Create a lineage node for an entity
  * `create-relation`: Create a lineage relationship for 2 entities
  * `delete-node`: Delete a lineage node for an entity
  * `delete-relation`: Deletes a single relationship based on...
  * `infer-relations`: Given an upstream and downstream path,...



  Create a lineage node for an entity

  **Usage**:

  ```console
  $ bigeye lineage create-node [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-en, --entity_name TEXT`: The fully qualified table name or name of the tableau workbook  [required]
  * `-in, --int_name TEXT`: The name of the BI connection (required for entities outside of Bigeye)
  * `--help`: Show this message and exit.



  Create a lineage relationship for 2 entities

  **Usage**:

  ```console
  $ bigeye lineage create-relation [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-up, --upstream TEXT`: The fully qualified table name  [required]
  * `-down, --downstream TEXT`: The fully qualified table name  [required]
  * `-co, --column_overrides TEXT`: The list of column overrides, formatted as upstream_column_name:downstream_column_name
  * `--help`: Show this message and exit.



  Delete a lineage node for an entity

  **Usage**:

  ```console
  $ bigeye lineage delete-node [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-en, --entity_name TEXT`: The fully qualified table name or name of the tableau workbook  [required]
  * `-in, --int_name TEXT`: The name of the BI connection (required for entities outside of Bigeye)
  * `--help`: Show this message and exit.



  Deletes a single relationship based on relation ID or all relationships for a node by name.

  **Usage**:

  ```console
  $ bigeye lineage delete-relation [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-en, --entity_name TEXT`: The fully qualified table name or name of the tableau workbook
  * `-rid, --relation_id INTEGER`: The relationship ID to delete  [default: 0]
  * `-in, --int_name TEXT`: The name of the BI connection (required for entities outside of Bigeye)
  * `--help`: Show this message and exit.



  Given an upstream and downstream path, infers lineage based on table/column names and creates the edges.

  **Usage**:

  ```console
  $ bigeye lineage infer-relations [OPTIONS]
  ```

  **Options**:

  * `-b, --bigeye_conf TEXT`: Bigeye Credential File Path  [env var: BIGEYE_API_CRED_FILE]
  * `-c, --config_file TEXT`: Bigeye CLI Configuration File Path  [env var: BIGEYE_API_CONFIG_FILE]
  * `-w, --workspace TEXT`: Name of the workspace configuration.  [env var: BIGEYE_WORKSPACE; default: DEFAULT]
  * `-upstream, --upstream_selector TEXT`: The pattern of tables in the upstream source to select. Wildcard (*) indicates all tables or all schemas, e.g. source_1.*.* would be all schemas in source_1.
  * `-downstream, --downstream_selector TEXT`: The pattern of tables in the downstream source to select. Wildcard (*) indicates all tables or all schemas, e.g. source_2.*.* would be all schemas in source_2.
  * `-mt, --match_type [strict|fuzzy]`: How to match tables between the source and target destinations. Strict will only create relations if table names match exactly, Fuzzy will attempt to create relations using a fuzzy match.  [default: strict]
  * `-lc, --lineage_conf TEXT`: The path to a Simple Lineage configuration file.
  * `-purge, --purge_lineage`: Purge all lineage edges.
  * `--help`: Show this message and exit.


