Full Code of gruns/icecream for AI

master 1d3858e4346e cached
24 files
108.5 KB
28.3k tokens
191 symbols
1 requests
Download .txt
Repository: gruns/icecream
Branch: master
Commit: 1d3858e4346e
Files: 24
Total size: 108.5 KB

Directory structure:
gitextract__hqhlpvx/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── changelog.txt
├── failures-to-investigate/
│   ├── freshsales.py
│   ├── freshsales2.py
│   └── freshsales3.py
├── icecream/
│   ├── __init__.py
│   ├── __version__.py
│   ├── builtins.py
│   ├── coloring.py
│   ├── icecream.py
│   └── py.typed
├── pyproject.toml
├── setup.cfg
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── install_test_import.py
│   ├── test_icecream.py
│   └── test_install.py
└── tox.ini

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

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
jobs:
  build:
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false
      matrix:
        include:
          - python-version: '3.8'
            toxenv: py38
          - python-version: '3.9'
            toxenv: py39
          - python-version: '3.10'
            toxenv: py310
          - python-version: '3.11'
            toxenv: py311
          - python-version: '3.12'
            toxenv: py312
          - python-version: '3.13'
            toxenv: py313
          - python-version: 'pypy-3.10'
            toxenv: pypy3
          - python-version: '3.9'
            toxenv: mypy
    steps:
      - uses: actions/checkout@v6
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install tox
        run: pip install tox
      - name: Tox
        run: tox
        env:
          TOXENV: ${{ matrix.toxenv }}


================================================
FILE: .gitignore
================================================
*~
.#*
\#*
.tox
dist/
.eggs/
build/
*.pyc
*.pyo
*.egg
*.egg-info
.aider*
playground


================================================
FILE: LICENSE.txt
================================================
Copyright 2018 Ansgar Grunseid

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: MANIFEST.in
================================================
include LICENSE.txt README.md
prune tests


================================================
FILE: README.md
================================================
<h1 align="center">
  <img src="logo.svg" width="220px" height="370px" alt="IceCream">
</h1>

<p align="center">
  <a href="https://pypi.python.org/pypi/icecream"><img src="https://badge.fury.io/py/icecream.svg"></a>
  <a href="https://github.com/gruns/icecream/actions/workflows/ci.yml"><img src="https://github.com/gruns/icecream/actions/workflows/ci.yml/badge.svg"></a>
  <a href="/LICENSE.txt"><img src="https://img.shields.io/pypi/l/icecream.svg"></a>
  <a href="https://pypi.python.org/pypi/icecream"><img src="https://img.shields.io/pypi/pyversions/icecream.svg"></a>
</p>


### IceCream — Never use print() to debug again

Do you ever use `print()` or `log()` to debug your code? Of course you
do. IceCream, or `ic` for short, makes print debugging a little sweeter.

`ic()` is like `print()`, but better:

  1. It prints both variables and expressions along with their values.
  2. It's 60% faster to type.
  3. Data structures are formatted and pretty printed.
  4. Output is syntax highlighted.
  5. It optionally includes program context: filename, line number, and
     parent function.

IceCream is well tested, [permissively licensed](LICENSE.txt), and supports Python 3 and PyPy3.

IceCream is maintained by [Jakeroid (Ivan Karabadzhak)](https://github.com/Jakeroid), with support from the confidential computing folks at [🌖 Lunal](https://lunal.dev/).


### Inspect Variables

Have you ever printed variables or expressions to debug your program? If
you've ever typed something like

```python
print(foo('123'))
```

or the more thorough

```python
print("foo('123')", foo('123'))
```

then `ic()` will put a smile on your face. With arguments, `ic()`
inspects itself and prints both its own arguments and the values of
those arguments.

```python
from icecream import ic

def foo(i):
    return i + 333

ic(foo(123))
```

Prints

```
ic| foo(123): 456
```

Similarly,

```python
d = {'key': {1: 'one'}}
ic(d['key'][1])

class klass():
    attr = 'yep'
ic(klass.attr)
```

Prints

```
ic| d['key'][1]: 'one'
ic| klass.attr: 'yep'
```

Just give `ic()` a variable or expression and you're done. Easy.


### Inspect Execution

Have you ever used `print()` to determine which parts of your program are
executed, and in which order they're executed? For example, if you've ever added
print statements to debug code like

```python
def foo():
    print(0)
    first()

    if expression:
        print(1)
        second()
    else:
        print(2)
        third()
```

then `ic()` helps here, too. Without arguments, `ic()` inspects itself and
prints the calling filename, line number, and parent function.

```python
from icecream import ic

def foo():
    ic()
    first()

    if expression:
        ic()
        second()
    else:
        ic()
        third()
```

Prints

```
ic| example.py:4 in foo()
ic| example.py:11 in foo()
```

Just call `ic()` and you're done. Simple.


### Return Value

`ic()` returns its argument(s), so `ic()` can easily be inserted into
pre-existing code.

```pycon
>>> a = 6
>>> def half(i):
>>>     return i / 2
>>> b = half(ic(a))
ic| a: 6
>>> ic(b)
ic| b: 3
```


### Miscellaneous

`ic.format(*args)` is like `ic()` but the output is returned as a string instead
of written to stderr.

```pycon
>>> from icecream import ic
>>> s = 'sup'
>>> out = ic.format(s)
>>> print(out)
ic| s: 'sup'
```

Additionally, `ic()`'s output can be entirely disabled, and later re-enabled, with
`ic.disable()` and `ic.enable()` respectively.

```python
from icecream import ic

ic(1)

ic.disable()
ic(2)

ic.enable()
ic(3)
```

Prints

```
ic| 1: 1
ic| 3: 3
```

`ic()` continues to return its arguments when disabled, of course; no existing
code with `ic()` breaks.


### Import Tricks

To make `ic()` available in every file without needing to be imported in
every file, you can `install()` it. For example, in a root `A.py`:

```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from icecream import install
install()

from B import foo
foo()
```

and then in `B.py`, which is imported by `A.py`, just call `ic()`:

```python
# -*- coding: utf-8 -*-

def foo():
    x = 3
    ic(x)
```

`install()` adds `ic()` to the
[builtins](https://docs.python.org/3.8/library/builtins.html) module,
which is shared amongst all files imported by the interpreter.
Similarly, `ic()` can later be `uninstall()`ed, too.

`ic()` can also be imported in a manner that fails gracefully if
IceCream isn't installed, like in production environments (i.e. not
development). To that end, this fallback import snippet may prove
useful:

```python
try:
    from icecream import ic
except ImportError:  # Graceful fallback if IceCream isn't installed.
    ic = lambda *a: None if not a else (a[0] if len(a) == 1 else a)  # noqa
```


### Configuration

`ic.configureOutput(prefix, outputFunction, argToStringFunction,
includeContext, contextAbsPath)` controls `ic()`'s output.

`prefix`, if provided, adopts a custom output prefix. `prefix` can be a
string, like

```pycon
>>> from icecream import ic
>>> ic.configureOutput(prefix='hello -> ')
>>> ic('world')
hello -> 'world'
```

or a function.

```pycon
>>> import time
>>> from icecream import ic
>>>  
>>> def unixTimestamp():
>>>     return '%i |> ' % int(time.time())
>>>
>>> ic.configureOutput(prefix=unixTimestamp)
>>> ic('world')
1519185860 |> 'world': 'world'
```

`prefix`'s default value is `ic| `.

`outputFunction`, if provided, is called once for every `ic()` call with
`ic()`'s output, as a string, instead of that string being written to
stderr (the default).

```pycon
>>> import logging
>>> from icecream import ic
>>>
>>> def warn(s):
>>>     logging.warning("%s", s)
>>>
>>> ic.configureOutput(outputFunction=warn)
>>> ic('eep')
WARNING:root:ic| 'eep': 'eep'
```

`argToStringFunction`, if provided, is called with argument values to be
serialized to displayable strings. The default is PrettyPrint's
[pprint.pformat()](https://docs.python.org/3/library/pprint.html#pprint.pformat),
but this can be changed to, for example, handle non-standard datatypes
in a custom fashion.

```pycon
>>> from icecream import ic
>>>
>>> def toString(obj):
>>>    if isinstance(obj, str):
>>>        return '[!string %r with length %i!]' % (obj, len(obj))
>>>    return repr(obj)
>>>
>>> ic.configureOutput(argToStringFunction=toString)
>>> ic(7, 'hello')
ic| 7: 7, 'hello': [!string 'hello' with length 5!]
```

The default `argToStringFunction` is `icecream.argumentToString`, and
has methods to `register` and `unregister` functions to be dispatched
for specific classes using `functools.singledispatch`. It also has a
`registry` property to view registered functions.

```pycon
>>> from icecream import ic, argumentToString
>>> import numpy as np
>>>
>>> # Register a function to summarize numpy array
>>> @argumentToString.register(np.ndarray)
>>> def _(obj):
>>>     return f"ndarray, shape={obj.shape}, dtype={obj.dtype}"
>>>
>>> x = np.zeros((1, 2))
>>> ic(x)
ic| x: ndarray, shape=(1, 2), dtype=float64
>>>
>>> # View registered functions
>>> argumentToString.registry
mappingproxy({object: <function icecream.icecream.argumentToString(obj)>,
              numpy.ndarray: <function __main__._(obj)>})
>>>
>>> # Unregister a function and fallback to the default behavior
>>> argumentToString.unregister(np.ndarray)
>>> ic(x)
ic| x: array([[0., 0.]])
```

`includeContext`, if provided and True, adds the `ic()` call's filename,
line number, and parent function to `ic()`'s output.

```pycon
>>> from icecream import ic
>>> ic.configureOutput(includeContext=True)
>>>
>>> def foo():
>>>   i = 3
>>>   ic(i)
>>> foo()
ic| example.py:12 in foo()- i: 3
```

`includeContext` is False by default.

`contextAbsPath`, if provided and True, outputs absolute filepaths, like
`/path/to/foo.py`, over just filenames, like `foo.py`, when `ic()` is
called with `includeContext == True`. This is useful when debugging
multiple files that share the same filename(s). Moreover, some editors,
like VSCode, turn absolute filepaths into clickable links that open the
file where `ic()` was called.

```pycon
>>> from icecream import ic
>>> ic.configureOutput(includeContext=True, contextAbsPath=True)
>>>
>>> i = 3
>>>
>>> def foo():
>>>   ic(i)
>>> foo()
ic| /absolute/path/to/example.py:12 in foo()- i: 3
>>>
>>> ic.configureOutput(includeContext=True, contextAbsPath=False)
>>>
>>> def foo():
>>>   ic(i)
>>> foo()
ic| example.py:18 in foo()- i: 3
```

`contextAbsPath` is False by default.

If you want to use icecream with multiple log levels, like with Python’s
`logging` module, you can use `ic.format()` to integrate icecream’s
debugging with your logger:

```python
import logging
from icecream import ic

foo = 'bar'
logging.debug(ic.format(foo))
```

❕ This is a bit clunky. Would you prefer built-in log level support in
icecream? If so, please share your thoughts in
[issue](https://github.com/gruns/icecream/issues/146).


### Installation

Installing IceCream with pip is easy.

```
$ pip install icecream
```


### Related Python libraries

`ic()` uses [**`executing`**](https://github.com/alexmojaki/executing)
by [**@alexmojaki**](https://github.com/alexmojaki) to reliably locate
`ic()` calls in Python source. It's magic.


### IceCream in Other Languages

Delicious IceCream should be enjoyed in every language.

- Dart: [icecream](https://github.com/HallerPatrick/icecream)
- Rust: [icecream-rs](https://github.com/ericchang00/icecream-rs)
- Node.js: [node-icecream](https://github.com/jmerle/node-icecream)
- C++: [IceCream-Cpp](https://github.com/renatoGarcia/icecream-cpp)
- C99: [icecream-c](https://github.com/chunqian/icecream-c)
- PHP: [icecream-php](https://github.com/ntzm/icecream-php)
- Go: [icecream-go](https://github.com/WAY29/icecream-go)
- Ruby: [Ricecream](https://github.com/nodai2hITC/ricecream)
- Java: [icecream-java](https://github.com/Akshay-Thakare/icecream-java)
- R: [icecream](https://github.com/lewinfox/icecream)
- Lua: [icecream-lua](https://github.com/wlingze/icecream-lua)
- Clojure(Script): [icecream-cljc](https://github.com/Eigenbahn/icecream-cljc)
- Bash: [IceCream-Bash](https://github.com/jtplaarj/IceCream-Bash)
- SystemVerilog: [icecream_sv](https://github.com/xver/icecream_sv)
- GameMaker Language: [GMIceCream](https://github.com/dicksonlaw583/GMIceCream)

If you'd like a similar `ic()` function in your favorite language, please open a
pull request! IceCream's goal is to sweeten print debugging with a handy-dandy
`ic()` function in every language.


================================================
FILE: changelog.txt
================================================
================================================================================
v2.1.10
================================================================================
Improved: This change excludes the test folder from wheels. 

Big thanks to the community! This release was made possible by the people
  who contributed to the library.

================================================================================
v2.1.9
================================================================================
Removed: Support for Python 3.8.  
Fixed: Issues #229 and #60, which means improved lists output.  

Big thanks to the community! This release was made possible by the people
  who contributed to the library.

================================================================================
v2.1.8
================================================================================
Added: You can pass a pre-configured ic instance to builtins.
Added: You can configure IceCream to output to either stdout or stderr.

Big thanks to the community! This release was made possible by the people
  who contributed to the library.

================================================================================
v2.1.7
================================================================================
Added: Configurable line wrap length.
Improved: The package no longer includes tests in the production installation.

================================================================================
v2.1.6
================================================================================
Fixed: Pretty-printing of SymPy (and similar) objects.

Previously, calling ic() on structures containing SymPy objects could raise
  a TypeError because pprint.pformat(sort_dicts=True) attempted to sort
  unorderable keys. IceCream now keeps sort_dicts=True on the fast path and
  falls back to sort_dicts=False when pprint raises, ensuring robust output
  without crashes.

================================================================================
 v2.1.5
================================================================================
Changed: Improved printing for variables of type `str`.

Fixed issues that affected the output of multiline strings and strings
  containing special characters such as escaped newlines and tabs.

Strings are now printed exactly as they are, faithfully representing their
  actual value.

================================================================================
 v2.1.4
================================================================================
Changed: Drop support for all Python versions prior to Python 3.8, which
  are now long past EOL. Notably: Python 2 is no longer supported.
Changed: Update the 'executing' dependency to >= v2.1.0 to improve
  source code analysis and support Python 3.13.

================================================================================
 v2.1.3
================================================================================
Added: The contextAbsPath= parameter to ic.configureOutput() which, when
  True, outputs absolute paths, like /path/to/foo.py, instead of just
  filenames, like foo.py. See https://github.com/gruns/icecream/pull/122.
  Huge thank you to @HelinXu!
Changed: Raise TypeError if no arguments are provided to
  ic.configureOutput().

================================================================================
 v2.1.2
================================================================================
Added: Ability to register and unregister singledispatch argumentToString
  functions. See https://github.com/gruns/icecream/pull/115. Huge thank you
  to @atusy!

================================================================================
 v2.1.1
================================================================================
Added: Support for Python 3.9.
Changed: Use timestamps in the local timezone instead of less helpful
  UTC timestamps.

================================================================================
 v2.1.0
================================================================================
Added: install() and uninstall() functions that add or remove ic() from
  the builtins module.
Changed: Switch to ast.literal_eval() to determine if an argument and
  value are the same, and thus only the value should be output. Huge
  thank you to Ed Cardinal and Alex Hall.

================================================================================
 v2.0.0
================================================================================
Added: Support for Python 3.8.
Removed: Support for Python 3.4.
Changed: Switched core AST parsing engine to Alex Hall's executing
  (https://github.com/alexmojaki/executing). Huge thank you to Alex Hall.
Changed: Whitespace in arguments is no longer collapsed. Indentation in
  multiline arguments is now preserved.

================================================================================
 v1.5.0
================================================================================
Fixed: Support multiline container arguments. e.g.
  ic([a,
        b])
Fixed: Include LICENSE.txt in source distributions.
Changed: Collapse argument whitespace, e.g. ic([ a,  b ]) -> ic| [a, b].

================================================================================
 v1.4.0
================================================================================
Added: Colorize output with pygments.
Added: Test Python style with pycodestyle.
Fixed: Parse and print tuple arguments correctly, e.g. ic((a, b)).
Fixed: Fail gracefully when the underlying source code changes during execution.
Changed: Print values (e.g. 1, 'foo', etc) by themselves, nonredundantly. For
  example, ic(3) now prints 'ic| 3' instead of 'ic| 3: 3'.

================================================================================
 v1.3.1
================================================================================
Removed: Support for Python 3.3, which reached EOL on 2017-09-29.
Fixed: ic() invocations that fail to find or access source code (e.g. eval(),
  exec(), python -i, etc) now print an error message instead of throwing an
  IOError (Python 2) or OSError (Python 3).

================================================================================
 v1.3
================================================================================
First release.


This changelog wasn't maintained prior to v1.3.


================================================
FILE: failures-to-investigate/freshsales.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright _!_
#
# License _!_

from os.path import abspath, dirname, join as pjoin
import pprint
import sys
import time

import requests
from icecream import ic

_corePath = abspath(pjoin(dirname(__file__), '../'))
if _corePath not in sys.path:
    sys.path.append(_corePath)
from common.utils import lget


DEFAULT_FIRST_NAME = 'there'
DEFAULT_LAST_NAME = '-'
FRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'
FS_API_URL = 'https://arcindustriesinc.freshsales.io/api'
FS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}


#
# FreshSales' Contact field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/contacts/fields"
#
#
# FreshSales' Lead field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/leads/fields"
#
# FreshSales' Company field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields"
#


def splitName(name):
    # Intelligently split <name> into first and last name, if provided.
    #   '' -> firstName: '', lastName: '-'
    #   'Susan' -> firstName: 'Susan', lastName: '-'
    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'
    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'
    toks = name.split(None, 1)
    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)
    lastName = lget(toks, 1, DEFAULT_LAST_NAME)

    return firstName, lastName


def lookupFullContact(contact):
    contactId = contact['id']
    resp = requests.get(
        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',
        headers=FS_AUTH_HEADERS)
    contact = (resp.json() or {}).get('contact')
    return contact


def findFirstContactWithEmail(emailAddr):
    return _findFirstEntityOf('contact', 'email', emailAddr)
def findFirstCompanyWithWebsite(websiteUrl):
    return _findFirstEntityOf('sales_account', 'website', websiteUrl)
def _findFirstEntityOf(entityType, query, queryValue):
    url = f'{FS_API_URL}/lookup?f={query}&entities={entityType}'
    from furl import furl
    ic(url, furl(f'{FS_API_URL}/lookup?f={query}&entities={entityType}').set(
        {'q': queryValue}).url)

    resp = requests.get(
        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',
        params={'q': queryValue}, headers=FS_AUTH_HEADERS)
    entities = (
        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])

    entity = lget(entities, 0)
    return entity


def createNote(entityType, entityId, message):
    data = {
        'note': {
            'description': message,
            'targetable_id': entityId,
            'targetable_type': entityType,
            }
        }
    resp = requests.post(
        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)

    if resp.status_code != 201:
        err = f'Failed to create {entityType} note for id {entityId}.'
        raise RuntimeError(err)


def createLead(data):
    return _createEntity('lead', data)

def createContact(data):
    ANSGAR_GRUNSEID = 9000013180
    data.setdefault('owner_id', ANSGAR_GRUNSEID)
    return _createEntity('contact', data)

def createCompany(data):
    return _createEntity('sales_account', data)

def _createEntity(entityType, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType}s'
    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code not in [200, 201]:
        raise RuntimeError(f'Failed to create new {entityType}.')

    entity = (resp.json() or {}).get(entityType)
    return entity


def updateLead(leadId, data):
    return _updateEntity('lead', leadId, data)

def updateContact(contactId, data):
    return _updateEntity('contact', contactId, data)

def updateCompany(companyId, data):
    return _updateEntity('sales_account', companyId, data)

def _updateEntity(entityType, entityId, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'
    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code != 200:
        err = f'Failed to update {entityType.title()} with id {entityId}.'
        raise RuntimeError(err)

    entity = (resp.json() or {}).get(entityType)
    return entity


def lookupContactsInView(viewId):
    return _lookupEntitiesInView('contact', viewId)

def _lookupEntitiesInView(entityType, viewId):
    entities = []

    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'
    def pageUrl(pageNo):
        return url + f'?page={pageNo}'

    resp = requests.get(url, headers=FS_AUTH_HEADERS)
    js = resp.json()
    entities += js.get(f'{entityType}s')
    totalPages = js.get('meta', {}).get('total_pages')

    for pageNo in range(2, totalPages + 1):
        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)
        entities += (resp.json() or {}).get(f'{entityType}s')

    return entities


def unsubscribeContact(contact, reasons):
    UNSUBSCRIBED = 9000159966
    updateContact(contact['id'], {
        'do_not_disturb': True,
        'contact_status_id': UNSUBSCRIBED,
    })

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(reasons)
    note = (
        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '
        'because:\n'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def optContactIn(contact):
    OPTED_IN = 9000159976
    updateContact(contact['id'], {
        'contact_status_id': OPTED_IN,
        })


def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
    if 'sales_accounts' not in contact:
        contact = lookupFullContact(contact)

    companyToAdd = None
    companies = contact.get('sales_accounts', [])
    company = findFirstCompanyWithWebsite(websiteUrl)
    if company:
        companyId = company['id']
        alreadyRelated = any(companyId == c['id'] for c in companies)
        if not alreadyRelated:
            companyToAdd = company
    else:
        companyToAdd = createCompany({
            'name': websiteUrl,
            'website': websiteUrl,
            })

    if companyToAdd:
        companyData = {
            'id': companyToAdd['id'],
            # There can only be one primary Company associated with a
            # Contact. See https://www.freshsales.io/api/#create_contact.
            'is_primary': False if companies else True,
            }
        companies.append(companyData)

    updateContact(contact['id'], { 'sales_accounts': companies })

    return company or companyToAdd


def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955
    updateContact(contact['id'], {
        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def noteContactSubmittedPepSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    
    PEP = 9000004543
    updateContact(contact['id'], {
        'custom_field': {
            'cf_product': 'Pep',
            },
        })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] "
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):
    INDIE_HACKERS = 9000321821
    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)

def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):
    firstName, lastName = splitName(name)

    SUSPECT = 9000073090
    contact = createContact({
        'email': emailAddr,
        'first_name': firstName,
        'last_name': lastName,
        'contact_status_id': SUSPECT,
        'lead_source_id': leadSourceId,
    })

    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(noteData)
    note = (
        f'This Contact was crawled and created on [{dateStr}]. '
        'Other data:'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def createSplashPageLead(name, emailAddr, websiteUrl):
    firstName, lastName = splitName(name)

    INTERESTED = 9000057526
    ARC_IO_SIGN_UP_FORM = 9000315608
    lead = createLead({
        'first_name': firstName,
        'last_name': lastName,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on arc.io with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def createPepSplashPageLead(emailAddr, websiteUrl):
    PEP = 9000004543
    INTERESTED = 9000057526
    PEP_SIGN_UP_FORM = 9000321929
    lead = createLead({
        'first_name': DEFAULT_FIRST_NAME,
        'last_name': DEFAULT_LAST_NAME,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'deal': {
            'deal_product_id': PEP,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': PEP_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on pep.dev with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
    raise NotImplementedError

    # TODO(grun): Finish me.

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        note = (
            f'The widget for Arc account with email {emailAddr} was just seen '
            f'live for the first seen for the first time live on {seenOnUrl}.')
        createNote('Contact', contact['id'], note)
    else:
        # TODO(grun): Log this scenario, which means someone added Arc's widget
        # to someone
        ic()


# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()
# functions, like how the above handle*() functions were refactored.
def handleWordPressPluginInstall(emailAddr, websiteUrl):
    WORDPRESS = 9000321857
    ALPHA_CODE = 9000124404

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        updateContact(contact['id'], {
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })
    else:
        contact = createContact({
            'email': emailAddr,
            'first_name': 'there',
            'last_name': websiteUrl,
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })

    CUSTOMER = 9000095000
    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    updateCompany(company['id'], {
        'business_type_id': CUSTOMER,
        'custom_field': {
            'cf_source': 'Wordpress',
            },
        })

    dateStr = time.ctime()
    note = (
        f"This Contact installed Arc's WordPress plugin at [{dateStr}] on "
        "website [{websiteUrl}].")
    createNote('Contact', contact['id'], note)


def handleWordPressPluginCreatedArcAccount(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    CUSTOMER = 9000066454
    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This WordPress Contact created their Arc account at [{dateStr}].')
    createNote('Contact', contact['id'], note)


def handleWordPressPluginUninstall(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    FORMER_CUSTOMER = 9000124405
    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')
    createNote('Contact', contact['id'], note)


if __name__ == '__main__':  # For development only.
    ic(findFirstCompanyWithWebsite('http://blockchainexamples.com'))


================================================
FILE: failures-to-investigate/freshsales2.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright _!_
#
# License _!_

from os.path import abspath, dirname, join as pjoin
import pprint
import sys
import time
from urllib.parse import urlparse

import requests
from icecream import ic

_corePath = abspath(pjoin(dirname(__file__), '../'))
if _corePath not in sys.path:
    sys.path.append(_corePath)
from common.utils import lget, stripStringStart


DEFAULT_FIRST_NAME = 'there'
DEFAULT_LAST_NAME = '-'
FRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'
FS_API_URL = 'https://arcindustriesinc.freshsales.io/api'
FS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}


#
# FreshSales' Contact field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/contacts/fields"
#
#
# FreshSales' Lead field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/leads/fields"
#
# FreshSales' Company field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields"
#


def splitName(name):
    # Intelligently split <name> into first and last name, if provided.
    #   '' -> firstName: '', lastName: '-'
    #   'Susan' -> firstName: 'Susan', lastName: '-'
    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'
    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'
    toks = name.split(None, 1)
    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)
    lastName = lget(toks, 1, DEFAULT_LAST_NAME)

    return firstName, lastName


def lookupFullContact(contact):
    contactId = contact['id']
    resp = requests.get(
        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',
        headers=FS_AUTH_HEADERS)
    contact = (resp.json() or {}).get('contact')
    return contact


def findFirstContactWithEmail(emailAddr):
    contacts = _findEntitiesWith('contact', 'email', emailAddr)
    return lget(contacts, 0)
def findFirstCompanyWithWebsite(websiteUrl):
    # FreshSales' API returns unrelated companies. For example, searching for
    # companies with website 'http://blockchainexamples.com', ie
    #
    #   ?f=website&entities=sales_account&q=http%3A%2F%2Fblockchainexamples.com
    #
    # returns https://arcindustriesinc.freshsales.io/accounts/9001963743 with
    # name 'http://culturetv.club' and website
    # 'http://104.225.221.170:8082'. Why? Who knows.
    #
    # As a workaround, filter all returned companies to verify that the domains
    # match.
    hostNoWww = lambda url: stripStringStart(urlparse(url).hostname, 'www.')
    allCompanies = _findEntitiesWith('sales_account', 'website', websiteUrl)
    ic(allCompanies)
    companies = [
        c for c in _findEntitiesWith('sales_account', 'website', websiteUrl)
        if ic(hostNoWww(c.get('website'))) == ic(hostNoWww(websiteUrl))]
    firstCompany = lget(companies, 0)
    return firstCompany
def _findEntitiesWith(entityType, query, queryValue):
    resp = requests.get(
        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',
        params={'q': queryValue}, headers=FS_AUTH_HEADERS)
    entities = (
        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])
    return entities


def createNote(entityType, entityId, message):
    data = {
        'note': {
            'description': message,
            'targetable_id': entityId,
            'targetable_type': entityType,
            }
        }
    resp = requests.post(
        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)

    if resp.status_code != 201:
        err = f'Failed to create {entityType} note for id {entityId}.'
        raise RuntimeError(err)


def createLead(data):
    return _createEntity('lead', data)

def createContact(data):
    ANSGAR_GRUNSEID = 9000013180
    data.setdefault('owner_id', ANSGAR_GRUNSEID)
    return _createEntity('contact', data)

def createCompany(data):
    return _createEntity('sales_account', data)

def _createEntity(entityType, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType}s'
    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code not in [200, 201]:
        raise RuntimeError(f'Failed to create new {entityType}.')

    entity = (resp.json() or {}).get(entityType)
    return entity


def updateLead(leadId, data):
    return _updateEntity('lead', leadId, data)

def updateContact(contactId, data):
    return _updateEntity('contact', contactId, data)

def updateCompany(companyId, data):
    return _updateEntity('sales_account', companyId, data)

def _updateEntity(entityType, entityId, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'
    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code != 200:
        err = f'Failed to update {entityType.title()} with id {entityId}.'
        raise RuntimeError(err)

    entity = (resp.json() or {}).get(entityType)
    return entity


def lookupContactsInView(viewId):
    return _lookupEntitiesInView('contact', viewId)

def _lookupEntitiesInView(entityType, viewId):
    entities = []

    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'
    def pageUrl(pageNo):
        return url + f'?page={pageNo}'

    resp = requests.get(url, headers=FS_AUTH_HEADERS)
    js = resp.json()
    entities += js.get(f'{entityType}s')
    totalPages = js.get('meta', {}).get('total_pages')

    for pageNo in range(2, totalPages + 1):
        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)
        entities += (resp.json() or {}).get(f'{entityType}s')

    return entities


def unsubscribeContact(contact, reasons):
    UNSUBSCRIBED = 9000159966
    updateContact(contact['id'], {
        'do_not_disturb': True,
        'contact_status_id': UNSUBSCRIBED,
    })

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(reasons)
    note = (
        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '
        'because:\n'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def optContactIn(contact):
    OPTED_IN = 9000159976
    updateContact(contact['id'], {
        'contact_status_id': OPTED_IN,
        })


def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
    if 'sales_accounts' not in contact:
        contact = lookupFullContact(contact)

    companyToAdd = None
    companies = contact.get('sales_accounts', [])
    company = findFirstCompanyWithWebsite(websiteUrl)
    if company:
        companyId = company['id']
        alreadyRelated = any(companyId == c['id'] for c in companies)
        if not alreadyRelated:
            companyToAdd = company
    else:
        companyToAdd = createCompany({
            'name': websiteUrl,
            'website': websiteUrl,
            })

    if companyToAdd:
        companyData = {
            'id': companyToAdd['id'],
            # There can only be one primary Company associated with a
            # Contact. See https://www.freshsales.io/api/#create_contact.
            'is_primary': False if companies else True,
            }
        companies.append(companyData)

    updateContact(contact['id'], { 'sales_accounts': companies })

    return company or companyToAdd


def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955
    updateContact(contact['id'], {
        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def noteContactSubmittedPepSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    
    PEP = 9000004543
    updateContact(contact['id'], {
        'custom_field': {
            'cf_product': 'Pep',
            },
        })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] "
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):
    INDIE_HACKERS = 9000321821
    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)

def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):
    firstName, lastName = splitName(name)

    SUSPECT = 9000073090
    contact = createContact({
        'email': emailAddr,
        'first_name': firstName,
        'last_name': lastName,
        'contact_status_id': SUSPECT,
        'lead_source_id': leadSourceId,
    })

    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(noteData)
    note = (
        f'This Contact was crawled and created on [{dateStr}]. '
        'Other data:'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def createSplashPageLead(name, emailAddr, websiteUrl):
    firstName, lastName = splitName(name)

    INTERESTED = 9000057526
    ARC_IO_SIGN_UP_FORM = 9000315608
    lead = createLead({
        'first_name': firstName,
        'last_name': lastName,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on arc.io with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def createPepSplashPageLead(emailAddr, websiteUrl):
    PEP = 9000004543
    INTERESTED = 9000057526
    PEP_SIGN_UP_FORM = 9000321929
    lead = createLead({
        'first_name': DEFAULT_FIRST_NAME,
        'last_name': DEFAULT_LAST_NAME,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'deal': {
            'deal_product_id': PEP,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': PEP_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on pep.dev with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
    raise NotImplementedError

    # TODO(grun): Finish me.

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        note = (
            f'The widget for Arc account with email {emailAddr} was just seen '
            f'live for the first seen for the first time live on {seenOnUrl}.')
        createNote('Contact', contact['id'], note)
    else:
        # TODO(grun): Log this scenario, which means someone added Arc's widget
        # to someone
        ic()


# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()
# functions, like how the above handle*() functions were refactored.
def handleWordPressPluginInstall(emailAddr, websiteUrl):
    WORDPRESS = 9000321857
    ALPHA_CODE = 9000124404

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        updateContact(contact['id'], {
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })
    else:
        contact = createContact({
            'email': emailAddr,
            'first_name': 'there',
            'last_name': websiteUrl,
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })

    CUSTOMER = 9000095000
    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    updateCompany(company['id'], {
        'business_type_id': CUSTOMER,
        'custom_field': {
            'cf_source': 'Wordpress',
            },
        })

    dateStr = time.ctime()
    note = (
        f"This Contact installed Arc's WordPress plugin at [{dateStr}] on "
        "website [{websiteUrl}].")
    createNote('Contact', contact['id'], note)


def handleWordPressPluginCreatedArcAccount(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    CUSTOMER = 9000066454
    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This WordPress Contact created their Arc account at [{dateStr}].')
    createNote('Contact', contact['id'], note)


def handleWordPressPluginUninstall(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    FORMER_CUSTOMER = 9000124405
    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')
    createNote('Contact', contact['id'], note)


if __name__ == '__main__':  # For development only.
    ic(findFirstCompanyWithWebsite('http://blockchainexamples.com'))
    #ic(findFirstCompanyWithWebsite('http://culturetv.club'))


================================================
FILE: failures-to-investigate/freshsales3.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright _!_
#
# License _!_

from os.path import abspath, dirname, join as pjoin
import pprint
import sys
import time
from urllib.parse import urlparse

import requests
from icecream import ic

_corePath = abspath(pjoin(dirname(__file__), '../'))
if _corePath not in sys.path:
    sys.path.append(_corePath)
from common.utils import lget, stripStringStart


DEFAULT_FIRST_NAME = 'there'
DEFAULT_LAST_NAME = '-'
FRESH_SALES_API_KEY = 'P3bYheaquAHH1_hNxhMUDQ'
FS_API_URL = 'https://arcindustriesinc.freshsales.io/api'
FS_AUTH_HEADERS = {'Authorization': 'Token token=P3bYheaquAHH1_hNxhMUDQ'}


#
# FreshSales' Contact field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/contacts/fields"
#
#
# FreshSales' Lead field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/leads/fields"
#
# FreshSales' Company field magic values looked up with
#
#   curl -H "Authorization: Token token=P3bYheaquAHH1_hNxhMUDQ" -H "Content-Type: application/json" -X GET "https://arcindustriesinc.freshsales.io/api/settings/sales_accounts/fields"
#


def splitName(name):
    # Intelligently split <name> into first and last name, if provided.
    #   '' -> firstName: '', lastName: '-'
    #   'Susan' -> firstName: 'Susan', lastName: '-'
    #   'Greg Borp' -> firstName: 'Greg', lastName: 'Borp'
    #   'Freddy van der Field' -> firstName: 'Freddy', lastName: 'van der Field'
    toks = name.split(None, 1)
    firstName = lget(toks, 0, DEFAULT_FIRST_NAME)
    lastName = lget(toks, 1, DEFAULT_LAST_NAME)

    return firstName, lastName


def lookupFullContact(contact):
    contactId = contact['id']
    resp = requests.get(
        f'{FS_API_URL}/contacts/{contactId}?include=sales_accounts',
        headers=FS_AUTH_HEADERS)
    contact = (resp.json() or {}).get('contact')
    return contact


def findFirstContactWithEmail(emailAddr):
    contacts = _findEntitiesWith('contact', 'email', emailAddr)
    return lget(contacts, 0)
def findFirstCompanyWithWebsite(websiteUrl):
    ic('before', websiteUrl)
    if ic(urlparse(websiteUrl).scheme) is None:
        websiteUrl = f'http://{websiteUrl}'
    ic('after', websiteUrl)

    # FreshSales' API returns unrelated companies. For example, searching for
    # companies with website 'http://blockchainexamples.com', ie
    #
    #   ?f=website&entities=sales_account&q=http%3A%2F%2Fblockchainexamples.com
    #
    # returns https://arcindustriesinc.freshsales.io/accounts/9001963743 with
    # name 'http://culturetv.club' and website
    # 'http://104.225.221.170:8082'. Why? Who knows.
    #
    # As a workaround, filter all returned companies to verify that the domains
    # match.
    hostNoWww = lambda url: stripStringStart(urlparse(url).hostname, 'www.')
    allCompanies = _findEntitiesWith('sales_account', 'website', websiteUrl)
    ic(allCompanies)
    ic(websiteUrl)
    companies = [
        c for c in _findEntitiesWith('sales_account', 'website', websiteUrl)
        if hostNoWww(c.get('website')) == hostNoWww(websiteUrl)]
    ic(companies)
    firstCompany = lget(companies, 0)
    return firstCompany
def _findEntitiesWith(entityType, query, queryValue):
    resp = requests.get(
        f'{FS_API_URL}/lookup?f={query}&entities={entityType}',
        params={'q': queryValue}, headers=FS_AUTH_HEADERS)
    entities = (
        resp.json() or {}).get(f'{entityType}s', {}).get(f'{entityType}s', [])
    return entities


def createNote(entityType, entityId, message):
    data = {
        'note': {
            'description': message,
            'targetable_id': entityId,
            'targetable_type': entityType,
            }
        }
    resp = requests.post(
        f'{FS_API_URL}/notes', json=data, headers=FS_AUTH_HEADERS)

    if resp.status_code != 201:
        err = f'Failed to create {entityType} note for id {entityId}.'
        raise RuntimeError(err)


def createLead(data):
    return _createEntity('lead', data)

def createContact(data):
    ANSGAR_GRUNSEID = 9000013180
    data.setdefault('owner_id', ANSGAR_GRUNSEID)
    return _createEntity('contact', data)

def createCompany(data):
    return _createEntity('sales_account', data)

def _createEntity(entityType, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType}s'
    resp = requests.post(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code not in [200, 201]:
        raise RuntimeError(f'Failed to create new {entityType}.')

    entity = (resp.json() or {}).get(entityType)
    return entity


def updateLead(leadId, data):
    return _updateEntity('lead', leadId, data)

def updateContact(contactId, data):
    return _updateEntity('contact', contactId, data)

def updateCompany(companyId, data):
    return _updateEntity('sales_account', companyId, data)

def _updateEntity(entityType, entityId, data):
    wrapped = {entityType: data}
    url = f'{FS_API_URL}/{entityType.lower()}s/{entityId}'
    resp = requests.put(url, json=wrapped, headers=FS_AUTH_HEADERS)

    if resp.status_code != 200:
        err = f'Failed to update {entityType.title()} with id {entityId}.'
        raise RuntimeError(err)

    entity = (resp.json() or {}).get(entityType)
    return entity


def lookupContactsInView(viewId):
    return _lookupEntitiesInView('contact', viewId)

def _lookupEntitiesInView(entityType, viewId):
    entities = []

    url = f'{FS_API_URL}/{entityType.lower()}s/view/{viewId}'
    def pageUrl(pageNo):
        return url + f'?page={pageNo}'

    resp = requests.get(url, headers=FS_AUTH_HEADERS)
    js = resp.json()
    entities += js.get(f'{entityType}s')
    totalPages = js.get('meta', {}).get('total_pages')

    for pageNo in range(2, totalPages + 1):
        resp = requests.get(pageUrl(pageNo), headers=FS_AUTH_HEADERS)
        entities += (resp.json() or {}).get(f'{entityType}s')

    return entities


def unsubscribeContact(contact, reasons):
    UNSUBSCRIBED = 9000159966
    updateContact(contact['id'], {
        'do_not_disturb': True,
        'contact_status_id': UNSUBSCRIBED,
    })

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(reasons)
    note = (
        f'This Contact unsubscribed on arc.io/unsubscribe at [{dateStr}] '
        'because:\n'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def optContactIn(contact):
    OPTED_IN = 9000159976
    updateContact(contact['id'], {
        'contact_status_id': OPTED_IN,
        })


def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
    if 'sales_accounts' not in contact:
        contact = lookupFullContact(contact)

    companyToAdd = None
    companies = contact.get('sales_accounts', [])
    company = findFirstCompanyWithWebsite(websiteUrl)
    if company:
        companyId = company['id']
        alreadyRelated = any(companyId == c['id'] for c in companies)
        if not alreadyRelated:
            companyToAdd = company
    else:
        companyToAdd = createCompany({
            'name': websiteUrl,
            'website': websiteUrl,
            })

    if companyToAdd:
        companyData = {
            'id': companyToAdd['id'],
            # There can only be one primary Company associated with a
            # Contact. See https://www.freshsales.io/api/#create_contact.
            'is_primary': False if companies else True,
            }
        companies.append(companyData)

    updateContact(contact['id'], { 'sales_accounts': companies })

    return company or companyToAdd


def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    SUBMITTED_ARC_IO_SIGN_UP_FORM = 9000159955
    updateContact(contact['id'], {
        'contact_status_id': SUBMITTED_ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f'This Contact submitted the sign up form on arc.io at [{dateStr}] '
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def noteContactSubmittedPepSplashPage(contact, websiteUrl):
    createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    
    PEP = 9000004543
    updateContact(contact['id'], {
        'custom_field': {
            'cf_product': 'Pep',
            },
        })

    dateStr = time.ctime()
    emailAddr = contact['email']
    note = (
        f"This Contact submitted Pep's sign up form on pep.dev at [{dateStr}] "
        f'with email address [{emailAddr}] and website [{websiteUrl}].')
    createNote('Contact', contact['id'], note)


def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteData):
    INDIE_HACKERS = 9000321821
    _createCrawledContact(name, emailAddr, websiteUrl, INDIE_HACKERS, noteData)

def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, noteData):
    firstName, lastName = splitName(name)

    SUSPECT = 9000073090
    contact = createContact({
        'email': emailAddr,
        'first_name': firstName,
        'last_name': lastName,
        'contact_status_id': SUSPECT,
        'lead_source_id': leadSourceId,
    })

    createAndOrAssociateCompanyWithContact(websiteUrl, contact)

    dateStr = time.ctime()
    reasonsStr = pprint.pformat(noteData)
    note = (
        f'This Contact was crawled and created on [{dateStr}]. '
        'Other data:'
        '\n'
        f'{reasonsStr}\n'
        '\n')
    createNote('Contact', contact['id'], note)


def createSplashPageLead(name, emailAddr, websiteUrl):
    firstName, lastName = splitName(name)

    INTERESTED = 9000057526
    ARC_IO_SIGN_UP_FORM = 9000315608
    lead = createLead({
        'first_name': firstName,
        'last_name': lastName,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': ARC_IO_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on arc.io with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def createPepSplashPageLead(emailAddr, websiteUrl):
    PEP = 9000004543
    INTERESTED = 9000057526
    PEP_SIGN_UP_FORM = 9000321929
    lead = createLead({
        'first_name': DEFAULT_FIRST_NAME,
        'last_name': DEFAULT_LAST_NAME,
        'email': emailAddr,
        'company': {
            'website': websiteUrl,
            },
        'deal': {
            'deal_product_id': PEP,
            },
        'lead_stage_id': INTERESTED,
        'lead_source_id': PEP_SIGN_UP_FORM,
    })

    dateStr = time.ctime()
    note = (
        f'This Lead was created on [{dateStr}] because they submitted '
        f'the sign up form on pep.dev with email address [{emailAddr}] '
        f'and website [{websiteUrl}].')
    createNote('Lead', lead['id'], note)


def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
    raise NotImplementedError

    # TODO(grun): Finish me.

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        note = (
            f'The widget for Arc account with email {emailAddr} was just seen '
            f'live for the first seen for the first time live on {seenOnUrl}.')
        createNote('Contact', contact['id'], note)
    else:
        # TODO(grun): Log this scenario, which means someone added Arc's widget
        # to someone
        ic()


# TODO(grun): Refactor and/or rename the below handleWordPressPlugin*()
# functions, like how the above handle*() functions were refactored.
def handleWordPressPluginInstall(emailAddr, websiteUrl):
    WORDPRESS = 9000321857
    ALPHA_CODE = 9000124404

    contact = findFirstContactWithEmail(emailAddr)
    if contact:
        updateContact(contact['id'], {
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })
    else:
        contact = createContact({
            'email': emailAddr,
            'first_name': 'there',
            'last_name': websiteUrl,
            'lead_source_id': WORDPRESS,
            'contact_status_id': ALPHA_CODE,
            })

    CUSTOMER = 9000095000
    company = createAndOrAssociateCompanyWithContact(websiteUrl, contact)
    updateCompany(company['id'], {
        'business_type_id': CUSTOMER,
        'custom_field': {
            'cf_source': 'Wordpress',
            },
        })

    dateStr = time.ctime()
    note = (
        f"This Contact installed Arc's WordPress plugin at [{dateStr}] on "
        "website [{websiteUrl}].")
    createNote('Contact', contact['id'], note)


def handleWordPressPluginCreatedArcAccount(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    CUSTOMER = 9000066454
    updateContact(contact['id'], { 'contact_status_id': CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This WordPress Contact created their Arc account at [{dateStr}].')
    createNote('Contact', contact['id'], note)


def handleWordPressPluginUninstall(emailAddr):
    contact = findFirstContactWithEmail(emailAddr)

    if not contact:
        return

    FORMER_CUSTOMER = 9000124405
    updateContact(contact['id'], { 'contact_status_id': FORMER_CUSTOMER })

    dateStr = time.ctime()
    note = (
        f'This Contact uninstalled their WordPress plugin at [{dateStr}].')
    createNote('Contact', contact['id'], note)


if __name__ == '__main__':  # For development only.
    #ic(findFirstCompanyWithWebsite('http://www.blockchainexamples.com'))
    #ic(findFirstCompanyWithWebsite('http://www.culturetv.club'))
    ic(findFirstCompanyWithWebsite('www.realizeventures.com'))


================================================
FILE: icecream/__init__.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

from .icecream import *  # noqa
from .builtins import install, uninstall

# Import all variables in __version__.py without explicit imports.
from . import __version__
globals().update(dict((k, v) for k, v in __version__.__dict__.items()))


================================================
FILE: icecream/__version__.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

__title__ = 'icecream'
__license__ = 'MIT'
__version__ = '2.1.10'
__author__ = 'Ansgar Grunseid'
__contact__ = 'grunseid@gmail.com'
__url__ = 'https://github.com/gruns/icecream'
__description__ = (
    'Never use print() to debug again: inspect variables, expressions, and '
    'program execution with a single, simple function call.')


================================================
FILE: icecream/builtins.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

from typing import Optional
import icecream

builtins = __import__('builtins')


def install(
    ic: str = 'ic',
    configured_ic: Optional[icecream.IceCreamDebugger] = None
) -> None:
    if configured_ic is None:
        configured_ic = icecream.ic
    setattr(builtins, ic, configured_ic)


def uninstall(ic: str = 'ic') -> None:
    delattr(builtins, ic)


================================================
FILE: icecream/coloring.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

from pygments.style import Style
from pygments.token import (
    Text, Name, Error, Other, String, Number, Keyword, Generic, Literal,
    Comment, Operator, Whitespace, Punctuation)


# Solarized: https://ethanschoonover.com/solarized/
class SolarizedDark(Style):

    BASE03  = '#002b36' # noqa
    BASE02  = '#073642' # noqa
    BASE01  = '#586e75' # noqa
    BASE00  = '#657b83' # noqa
    BASE0   = '#839496' # noqa
    BASE1   = '#93a1a1' # noqa
    BASE2   = '#eee8d5' # noqa
    BASE3   = '#fdf6e3' # noqa
    YELLOW  = '#b58900' # noqa
    ORANGE  = '#cb4b16' # noqa
    RED     = '#dc322f' # noqa
    MAGENTA = '#d33682' # noqa
    VIOLET  = '#6c71c4' # noqa
    BLUE    = '#268bd2' # noqa
    CYAN    = '#2aa198' # noqa
    GREEN   = '#859900' # noqa

    styles = {
        Text:                   BASE0,
        Whitespace:             BASE03,
        Error:                  RED,
        Other:                  BASE0,

        Name:                   BASE1,
        Name.Attribute:         BASE0,
        Name.Builtin:           BLUE,
        Name.Builtin.Pseudo:    BLUE,
        Name.Class:             BLUE,
        Name.Constant:          YELLOW,
        Name.Decorator:         ORANGE,
        Name.Entity:            ORANGE,
        Name.Exception:         ORANGE,
        Name.Function:          BLUE,
        Name.Property:          BLUE,
        Name.Label:             BASE0,
        Name.Namespace:         YELLOW,
        Name.Other:             BASE0,
        Name.Tag:               GREEN,
        Name.Variable:          ORANGE,
        Name.Variable.Class:    BLUE,
        Name.Variable.Global:   BLUE,
        Name.Variable.Instance: BLUE,

        String:                 CYAN,
        String.Backtick:        CYAN,
        String.Char:            CYAN,
        String.Doc:             CYAN,
        String.Double:          CYAN,
        String.Escape:          ORANGE,
        String.Heredoc:         CYAN,
        String.Interpol:        ORANGE,
        String.Other:           CYAN,
        String.Regex:           CYAN,
        String.Single:          CYAN,
        String.Symbol:          CYAN,

        Number:                 CYAN,
        Number.Float:           CYAN,
        Number.Hex:             CYAN,
        Number.Integer:         CYAN,
        Number.Integer.Long:    CYAN,
        Number.Oct:             CYAN,

        Keyword:                GREEN,
        Keyword.Constant:       GREEN,
        Keyword.Declaration:    GREEN,
        Keyword.Namespace:      ORANGE,
        Keyword.Pseudo:         ORANGE,
        Keyword.Reserved:       GREEN,
        Keyword.Type:           GREEN,

        Generic:                BASE0,
        Generic.Deleted:        BASE0,
        Generic.Emph:           BASE0,
        Generic.Error:          BASE0,
        Generic.Heading:        BASE0,
        Generic.Inserted:       BASE0,
        Generic.Output:         BASE0,
        Generic.Prompt:         BASE0,
        Generic.Strong:         BASE0,
        Generic.Subheading:     BASE0,
        Generic.Traceback:      BASE0,

        Literal:                BASE0,
        Literal.Date:           BASE0,

        Comment:                BASE01,
        Comment.Multiline:      BASE01,
        Comment.Preproc:        BASE01,
        Comment.Single:         BASE01,
        Comment.Special:        BASE01,

        Operator:               BASE0,
        Operator.Word:          GREEN,

        Punctuation:            BASE0,
    }


================================================
FILE: icecream/icecream.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

import ast
import enum
import inspect
import pprint
import sys
from types import FrameType
from typing import Optional, cast, Any, Callable, Generator, List, Sequence, Tuple, Type, Union, cast, Literal
import warnings
from datetime import datetime
import functools
from contextlib import contextmanager
from os.path import basename, realpath
from textwrap import dedent

import colorama
import executing
from pygments import highlight

# See https://gist.github.com/XVilka/8346728 for color support in various
# terminals and thus whether to use Terminal256Formatter or
# TerminalTrueColorFormatter.
from pygments.formatters import Terminal256Formatter
from pygments.lexers import PythonLexer as PyLexer, Python3Lexer as Py3Lexer

from .coloring import SolarizedDark


class Sentinel(enum.Enum):
    absent = object()


def bindStaticVariable(name: str, value: Any) -> Callable:
    def decorator(fn: Callable) -> Callable:
        setattr(fn, name, value)
        return fn
    return decorator


@bindStaticVariable('formatter', Terminal256Formatter(style=SolarizedDark))
@bindStaticVariable('lexer', Py3Lexer(ensurenl=False))
def colorize(s: str) -> str:
    self = colorize
    return highlight(
        s,
        cast(Py3Lexer, self.lexer),
        cast(Terminal256Formatter, self.formatter)
    )  # pyright: ignore[reportFunctionMemberAccess]


@contextmanager
def supportTerminalColorsInWindows() -> Generator:
    # Filter and replace ANSI escape sequences on Windows with equivalent Win32
    # API calls. This code does nothing on non-Windows systems.
    if sys.platform.startswith('win'):
        colorama.init()
        yield
        colorama.deinit()
    else:
        yield


def stderrPrint(*args: object) -> None:
    print(*args, file=sys.stderr)


def isLiteral(s: str) -> bool:
    try:
        ast.literal_eval(s)
    except Exception:
        return False
    return True


def colorizedStderrPrint(s: str) -> None:
    colored = colorize(s)
    with supportTerminalColorsInWindows():
        stderrPrint(colored)


def colorizedStdoutPrint(s: str) -> None:
    colored = colorize(s)
    with supportTerminalColorsInWindows():
        print(colored)


def safe_pformat(obj: object, *args: Any, **kwargs: Any) -> str:
    """pprint.pformat() with a couple of small safety/usability tweaks.

    In addition to the usual TypeError handling below, we special–case
    "medium sized" flat lists. For those, the standard pprint heuristics
    sometimes choose a one-item-per-line layout which makes the order of
    values hard to visually follow in ic()'s output. For such lists we
    prefer the more compact repr()-style representation.
    """

    def _pformat(extra_kwargs: Optional[dict] = None) -> str:
        # Helper so we always pass the same args/kwargs to pprint.
        final_kwargs = dict(kwargs)
        if extra_kwargs:
            final_kwargs.update(extra_kwargs)
        return pprint.pformat(obj, *args, **final_kwargs)

    try:
        # For flat lists we try a slightly wider layout first. This keeps
        # simple medium-sized lists on a single line in the common case.
        is_flat_list = (
            isinstance(obj, list)
            and not args
            and 'width' not in kwargs
            and not any(isinstance(el, (list, tuple, dict, set)) for el in obj)
        )
        if is_flat_list:
            formatted = _pformat({'width': 120})
        else:
            formatted = _pformat(None)
    except TypeError as e:
        # Sorting likely tripped on symbolic/elementwise comparisons.
        warnings.warn(f"pprint failed ({e}); retrying without dict sorting")
        try:
            # Py 3.8+: disable sorting globally for all nested dicts.
            return _pformat({'sort_dicts': False})
        except TypeError:
            # Py < 3.8: last-ditch, always works.
            return repr(obj)

    # Heuristic: if pprint decided to break a flat, medium-sized list across
    # many lines, fall back to repr() which keeps the list visually compact
    # and easier to read in ic()'s prefix/value layout.
    if is_flat_list and isinstance(obj, list) and 13 <= len(obj) <= 35:
        lines = formatted.splitlines()
        if len(lines) > 10:
            one_line = repr(obj)
            if len(one_line) <= 120:
                return one_line

    return formatted


DEFAULT_PREFIX = 'ic| '
DEFAULT_LINE_WRAP_WIDTH = 70  # Characters.
DEFAULT_CONTEXT_DELIMITER = '- '
DEFAULT_OUTPUT_FUNCTION = colorizedStderrPrint
DEFAULT_ARG_TO_STRING_FUNCTION = safe_pformat

"""
This info message is printed instead of the arguments when icecream
fails to find or access source code that's required to parse and analyze.
This can happen, for example, when

  - ic() is invoked inside a REPL or interactive shell, e.g. from the
    command line (CLI) or with python -i.

  - The source code is mangled and/or packaged, e.g. with a project
    freezer like PyInstaller.

  - The underlying source code changed during execution. See
    https://stackoverflow.com/a/33175832.
"""
NO_SOURCE_AVAILABLE_WARNING_MESSAGE = (
    'Failed to access the underlying source code for analysis. Was ic() '
    'invoked in a REPL (e.g. from the command line), a frozen application '
    '(e.g. packaged with PyInstaller), or did the underlying source code '
    'change during execution?')


def callOrValue(obj: object) -> object:
    return obj() if callable(obj) else obj


class Source(executing.Source):
    def get_text_with_indentation(self, node: ast.expr) -> str:
        result = self.asttokens().get_text(node)
        if '\n' in result:
            result = ' ' * node.first_token.start[1] + result  # type: ignore[attr-defined]
            result = dedent(result)
        result = result.strip()
        return result


def prefixLines(prefix: str, s: str, startAtLine: int = 0) -> List[str]:
    lines = s.splitlines()

    for i in range(startAtLine, len(lines)):
        lines[i] = prefix + lines[i]

    return lines


def prefixFirstLineIndentRemaining(prefix: str, s: str) -> List[str]:
    indent = ' ' * len(prefix)
    lines = prefixLines(indent, s, startAtLine=1)
    lines[0] = prefix + lines[0]
    return lines


def formatPair(prefix: str, arg: Union[str, Sentinel], value: str) -> str:
    if arg is Sentinel.absent:
        argLines = []
        valuePrefix = prefix
    else:
        argLines = prefixFirstLineIndentRemaining(prefix, arg)
        valuePrefix = argLines[-1] + ': '

    looksLikeAString = (value[0] + value[-1]) in ["''", '""']
    if looksLikeAString:  # Align the start of multiline strings.
        valueLines = prefixLines(' ', value, startAtLine=1)
        value = '\n'.join(valueLines)

    valueLines = prefixFirstLineIndentRemaining(valuePrefix, value)
    lines = argLines[:-1] + valueLines
    return '\n'.join(lines)


class _SingleDispatchCallable:
    def __call__(self, *_: object) -> str:
        # This is a marker class, not a real thing you should use
        raise NotImplementedError
    register: Callable[[Type], Callable]


def singledispatch(func: Callable) -> _SingleDispatchCallable:
    func = functools.singledispatch(func)

    # add unregister based on https://stackoverflow.com/a/25951784
    assert func.register.__closure__ is not None
    closure = dict(zip(func.register.__code__.co_freevars,
                       func.register.__closure__))
    registry = closure['registry'].cell_contents
    dispatch_cache = closure['dispatch_cache'].cell_contents

    def unregister(cls: Type) -> None:
        del registry[cls]
        dispatch_cache.clear()

    func.unregister = unregister  # type: ignore[attr-defined]
    return cast(_SingleDispatchCallable, func)


@singledispatch
def argumentToString(obj: object) -> str:
    s = DEFAULT_ARG_TO_STRING_FUNCTION(obj)
    s = s.replace('\\n', '\n')  # Preserve string newlines in output.
    return s


@argumentToString.register(str)
def _(obj: str) -> str:
    if '\n' in obj:
        return "'''" + obj + "'''"

    return "'" + obj.replace('\\', '\\\\') + "'"


class IceCreamDebugger:
    _pairDelimiter = ', '  # Used by the tests in tests/.
    lineWrapWidth = DEFAULT_LINE_WRAP_WIDTH
    contextDelimiter = DEFAULT_CONTEXT_DELIMITER

    def __init__(self, prefix: Union[str, Callable[[], str]] =DEFAULT_PREFIX,
                 outputFunction: Callable[[str], None]=DEFAULT_OUTPUT_FUNCTION,
                 argToStringFunction: Union[_SingleDispatchCallable, Callable[[Any], str]]=argumentToString, includeContext: bool=False,
                 contextAbsPath: bool=False):
        self.enabled = True
        self.prefix = prefix
        self.includeContext = includeContext
        self.outputFunction = outputFunction
        self.argToStringFunction = argToStringFunction
        self.contextAbsPath = contextAbsPath

    def __call__(self, *args: object) -> object:
        if self.enabled:
            currentFrame = inspect.currentframe()
            assert currentFrame is not None and currentFrame.f_back is not None
            callFrame = currentFrame.f_back
            self.outputFunction(self._format(callFrame, *args))

        if not args:  # E.g. ic().
            passthrough = None
        elif len(args) == 1:  # E.g. ic(1).
            passthrough = args[0]
        else:  # E.g. ic(1, 2, 3).
            passthrough = args

        return passthrough

    def format(self, *args: object) -> str:
        currentFrame = inspect.currentframe()
        assert currentFrame is not None and currentFrame.f_back is not None
        callFrame = currentFrame.f_back
        out = self._format(callFrame, *args)
        return out

    def _format(self, callFrame: FrameType, *args: object) -> str:
        prefix = cast(str, callOrValue(self.prefix))

        context = self._formatContext(callFrame)
        if not args:
            time = self._formatTime()
            out = prefix + context + time
        else:
            if not self.includeContext:
                context = ''
            out = self._formatArgs(
                callFrame, prefix, context, args)

        return out

    def _formatArgs(self, callFrame: FrameType, prefix: str, context: str, args: Sequence[object]) -> str:
        callNode = Source.executing(callFrame).node
        if callNode is not None:
            assert isinstance(callNode, ast.Call)
            source = cast(Source, Source.for_frame(callFrame))
            sanitizedArgStrs = [
                source.get_text_with_indentation(arg)
                for arg in callNode.args]
        else:
            warnings.warn(
                NO_SOURCE_AVAILABLE_WARNING_MESSAGE,
                category=RuntimeWarning, stacklevel=4)
            sanitizedArgStrs = [Sentinel.absent] * len(args)

        pairs = list(zip(sanitizedArgStrs, cast(List[str], args)))

        out = self._constructArgumentOutput(prefix, context, pairs)
        return out

    def _constructArgumentOutput(self, prefix: str, context: str, pairs: Sequence[Tuple[Union[str, Sentinel], str]]) -> str:
        def argPrefix(arg: str) -> str:
            return '%s: ' % arg

        pairs = [(arg, self.argToStringFunction(val)) for arg, val in pairs]
        # For cleaner output, if <arg> is a literal, eg 3, "a string",
        # b'bytes', etc, only output the value, not the argument and the
        # value, because the argument and the value will be identical or
        # nigh identical. Ex: with ic("hello"), just output
        #
        #   ic| 'hello',
        #
        # instead of
        #
        #   ic| "hello": 'hello'.
        #
        # When the source for an arg is missing we also only print the value,
        # since we can't know anything about the argument itself.
        pairStrs = [
            val if (arg is Sentinel.absent or isLiteral(arg))
            else (argPrefix(arg) + val)
            for arg, val in pairs]

        allArgsOnOneLine = self._pairDelimiter.join(pairStrs)
        multilineArgs = len(allArgsOnOneLine.splitlines()) > 1

        contextDelimiter = self.contextDelimiter if context else ''
        allPairs = prefix + context + contextDelimiter + allArgsOnOneLine
        firstLineTooLong = len(allPairs.splitlines()[0]) > self.lineWrapWidth

        if multilineArgs or firstLineTooLong:
            # ic| foo.py:11 in foo()
            #     multilineStr: 'line1
            #                    line2'
            #
            # ic| foo.py:11 in foo()
            #     a: 11111111111111111111
            #     b: 22222222222222222222
            if context:
                lines = [prefix + context] + [
                    formatPair(len(prefix) * ' ', arg, value)
                    for arg, value in pairs
                ]
            # ic| multilineStr: 'line1
            #                    line2'
            #
            # ic| a: 11111111111111111111
            #     b: 22222222222222222222
            else:
                argLines = [
                    formatPair('', arg, value)
                    for arg, value in pairs
                ]
                lines = prefixFirstLineIndentRemaining(prefix, '\n'.join(argLines))
        # ic| foo.py:11 in foo()- a: 1, b: 2
        # ic| a: 1, b: 2, c: 3
        else:
            lines = [prefix + context + contextDelimiter + allArgsOnOneLine]

        return '\n'.join(lines)

    def _formatContext(self, callFrame: FrameType) -> str:
        filename, lineNumber, parentFunction = self._getContext(callFrame)

        if parentFunction != '<module>':
            parentFunction = '%s()' % parentFunction

        context = '%s:%s in %s' % (filename, lineNumber, parentFunction)
        return context

    def _formatTime(self) -> str:
        now = datetime.now()
        formatted = now.strftime('%H:%M:%S.%f')[:-3]
        return ' at %s' % formatted

    def _getContext(self, callFrame: FrameType) -> Tuple[str, int, str]:
        frameInfo = inspect.getframeinfo(callFrame)
        lineNumber = frameInfo.lineno
        parentFunction = frameInfo.function

        filepath = (realpath if self.contextAbsPath else basename)(frameInfo.filename)  # type: ignore[operator]
        return filepath, lineNumber, parentFunction

    def enable(self) -> None:
        self.enabled = True

    def disable(self) -> None:
        self.enabled = False

    def use_stdout(self) -> None:
        self.outputFunction = colorizedStdoutPrint

    def use_stderr(self) -> None:
        self.outputFunction = colorizedStderrPrint

    def configureOutput(
        self: "IceCreamDebugger",
        prefix: Union[str, Literal[Sentinel.absent]] = Sentinel.absent,
        outputFunction: Union[Callable, Literal[Sentinel.absent]] = Sentinel.absent,
        argToStringFunction: Union[Callable, Literal[Sentinel.absent]] = Sentinel.absent,
        includeContext: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent,
        contextAbsPath: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent,
        lineWrapWidth: Union[bool, Literal[Sentinel.absent]] = Sentinel.absent
    ) -> None:
        noParameterProvided = all(
            v is Sentinel.absent for k, v in locals().items() if k != 'self')
        if noParameterProvided:
            raise TypeError('configureOutput() missing at least one argument')

        if prefix is not Sentinel.absent:
            self.prefix = prefix

        if outputFunction is not Sentinel.absent:
            self.outputFunction = outputFunction

        if argToStringFunction is not Sentinel.absent:
            self.argToStringFunction = argToStringFunction

        if includeContext is not Sentinel.absent:
            self.includeContext = includeContext

        if contextAbsPath is not Sentinel.absent:
            self.contextAbsPath = contextAbsPath

        if lineWrapWidth is not Sentinel.absent:
            self.lineWrapWidth = lineWrapWidth


ic = IceCreamDebugger()


================================================
FILE: icecream/py.typed
================================================


================================================
FILE: pyproject.toml
================================================
[tool.mypy]
show_error_codes=true
disallow_untyped_defs=true
disallow_untyped_calls=true
warn_redundant_casts=true

================================================
FILE: setup.cfg
================================================
[metadata]
license_files = LICENSE.txt


================================================
FILE: setup.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

import os
import sys
from os.path import dirname, join as pjoin
from setuptools import setup, find_packages, Command
from setuptools.command.test import test as TestCommand


meta = {}
with open(pjoin('icecream', '__version__.py')) as f:
    exec(f.read(), meta)


class Publish(Command):
    """Publish to PyPI with twine."""
    user_options = []

    def initialize_options(self) -> None:
        pass

    def finalize_options(self) -> None:
        pass

    def run(self) -> None:
        os.system('python3 setup.py sdist bdist_wheel')

        sdist = 'dist/icecream-%s.tar.gz' % meta['__version__']
        wheel = 'dist/icecream-%s-py3-none-any.whl' % meta['__version__']
        rc = os.system('twine upload "%s" "%s"' % (sdist, wheel))

        sys.exit(rc)


class RunTests(TestCommand):
    """
    Run the unit tests.

    By default, `python setup.py test` fails if tests/ isn't a Python
    module (that is, if the tests/ directory doesn't contain an
    __init__.py file). But the tests/ directory shouldn't contain an
    __init__.py file and tests/ shouldn't be a Python module. See

      http://doc.pytest.org/en/latest/goodpractices.html

    Running the unit tests manually here enables `python setup.py test`
    without tests/ being a Python module.
    """
    def run_tests(self) -> None:
        from unittest import TestLoader, TextTestRunner
        tests_dir = pjoin(dirname(__file__), 'tests')
        suite = TestLoader().discover(tests_dir)
        result = TextTestRunner().run(suite)
        sys.exit(0 if result.wasSuccessful() else -1)


setup(
    name=meta['__title__'],
    license=meta['__license__'],
    version=meta['__version__'],
    author=meta['__author__'],
    author_email=meta['__contact__'],
    url=meta['__url__'],
    description=meta['__description__'],
    long_description=(
        'Information and documentation can be found at '
        'https://github.com/gruns/icecream.'),
    platforms=['any'],
    packages=find_packages(exclude=['tests', 'tests.*']),
    include_package_data=True,
    package_data={'icecream': ['py.typed']},
    classifiers=[
        'License :: OSI Approved :: MIT License',
        'Natural Language :: English',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Libraries',
        'Development Status :: 5 - Production/Stable',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
        'Programming Language :: Python :: 3.11',
        'Programming Language :: Python :: 3.12',
        'Programming Language :: Python :: 3.13',
        'Programming Language :: Python :: 3.14',
        'Programming Language :: Python :: Implementation :: PyPy',
        'Programming Language :: Python :: Implementation :: CPython',
    ],
    tests_require=[
        'tox>=4',
    ],
    install_requires=[
        'colorama>=0.3.9',
        'pygments>=2.2.0',
        'executing>=2.1.0',
        'asttokens>=2.0.1',
    ],
    cmdclass={
        'test': RunTests,
        'publish': Publish,
    },
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/install_test_import.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

def runMe():
    x = 3
    ic(x)


================================================
FILE: tests/test_icecream.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

import sys
import unittest
import warnings

from io import StringIO
from contextlib import contextmanager
from os.path import basename, splitext, realpath

import icecream
from icecream import ic, argumentToString, stderrPrint, NO_SOURCE_AVAILABLE_WARNING_MESSAGE

TEST_PAIR_DELIMITER = '| '
MY_FILENAME = basename(__file__)
MY_FILEPATH = realpath(__file__)


a = 1
b = 2
c = 3


def noop(*args, **kwargs):
    return


def has_ansi_escape_codes(s):
    # Oversimplified, but ¯\_(ツ)_/¯. TODO(grun): Test with regex.
    return '\x1b[' in s


class FakeTeletypeBuffer(StringIO):
    """
    Extend StringIO to act like a TTY so ANSI control codes aren't stripped
    when wrapped with colorama's wrap_stream().
    """
    def isatty(self):
        return True


@contextmanager
def disable_coloring():
    originalOutputFunction = ic.outputFunction

    ic.configureOutput(outputFunction=stderrPrint)
    yield
    ic.configureOutput(outputFunction=originalOutputFunction)


@contextmanager
def configure_icecream_output(prefix=None, outputFunction=None,
                            argToStringFunction=None, includeContext=None,
                            contextAbsPath=None):
    oldPrefix = ic.prefix
    oldOutputFunction = ic.outputFunction
    oldArgToStringFunction = ic.argToStringFunction
    oldIncludeContext = ic.includeContext
    oldContextAbsPath = ic.contextAbsPath

    if prefix:
        ic.configureOutput(prefix=prefix)
    if outputFunction:
        ic.configureOutput(outputFunction=outputFunction)
    if argToStringFunction:
        ic.configureOutput(argToStringFunction=argToStringFunction)
    if includeContext:
        ic.configureOutput(includeContext=includeContext)
    if contextAbsPath:
        ic.configureOutput(contextAbsPath=contextAbsPath)

    yield

    ic.configureOutput(
        oldPrefix, oldOutputFunction, oldArgToStringFunction,
        oldIncludeContext, oldContextAbsPath)


@contextmanager
def capture_standard_streams():
    realStdout = sys.stdout
    realStderr = sys.stderr
    newStdout = FakeTeletypeBuffer()
    newStderr = FakeTeletypeBuffer()
    try:
        sys.stdout = newStdout
        sys.stderr = newStderr
        yield newStdout, newStderr
    finally:
        sys.stdout = realStdout
        sys.stderr = realStderr


def strip_prefix(line):
    if line.startswith(ic.prefix):
        line = line.strip()[len(ic.prefix):]
    return line


def line_is_context_and_time(line):
    line = strip_prefix(line)  # ic| f.py:33 in foo() at 08:08:51.389
    context, time = line.split(' at ')

    return (
        line_is_context(context) and
        len(time.split(':')) == 3 and
        len(time.split('.')) == 2)


def line_is_context(line):
    line = strip_prefix(line)  # ic| f.py:33 in foo()
    sourceLocation, function = line.split(' in ')  # f.py:33 in foo()
    filename, lineNumber = sourceLocation.split(':')  # f.py:33
    name, ext = splitext(filename)

    return (
        int(lineNumber) > 0 and
        ext in ['.py', '.pyc', '.pyo'] and
        name == splitext(MY_FILENAME)[0] and
        (function == '<module>' or function.endswith('()')))

def line_is_abs_path_context(line):
    line = strip_prefix(line)  # ic| /absolute/path/to/f.py:33 in foo()
    sourceLocation, function = line.split(' in ')  # /absolute/path/to/f.py:33 in foo()
    filepath, lineNumber = sourceLocation.split(':')  # /absolute/path/to/f.py:33
    path, ext = splitext(filepath)

    return (
        int(lineNumber) > 0 and
        ext in ['.py', '.pyc', '.pyo'] and
        path == splitext(MY_FILEPATH)[0] and
        (function == '<module>' or function.endswith('()')))

def line_after_context(line, prefix):
    if line.startswith(prefix):
        line = line[len(prefix):]

    toks = line.split(' in ', 1)
    if len(toks) == 2:
        rest = toks[1].split(' ')
        line = ' '.join(rest[1:])

    return line

def parse_output_into_pairs(out, err, assert_num_lines,
                            prefix=icecream.DEFAULT_PREFIX):
    if isinstance(out, StringIO):
        out = out.getvalue()
    if isinstance(err, StringIO):
        err = err.getvalue()

    assert not out

    lines = err.splitlines()
    if assert_num_lines:
        assert len(lines) == assert_num_lines

    line_pairs = []
    for line in lines:
        line = line_after_context(line, prefix)

        if not line:
            line_pairs.append([])
            continue

        pairStrs = line.split(TEST_PAIR_DELIMITER)
        pairs = [tuple(s.split(':', 1)) for s in pairStrs]
        # Indented line of a multiline value.
        if len(pairs[0]) == 1 and line.startswith(' '):
            arg, value = line_pairs[-1][-1]
            looksLikeAString = value[0] in ["'", '"']
            prefix = ((arg + ': ' if arg is not None else '')  # A multiline value
                      + (' ' if looksLikeAString else ''))
            dedented = line[len(ic.prefix) + len(prefix):]
            line_pairs[-1][-1] = (arg, value + '\n' + dedented)
        else:
            items = [
                (None, p[0].strip()) if len(p) == 1  # A value, like ic(3).
                else (p[0].strip(), p[1].strip())  # A variable, like ic(a).
                for p in pairs]
            line_pairs.append(items)

    return line_pairs


class TestIceCream(unittest.TestCase):
    def setUp(self):
        ic._pairDelimiter = TEST_PAIR_DELIMITER

    def test_metadata(self):
        def is_non_empty_string(s):
            return isinstance(s, str) and s
        assert is_non_empty_string(icecream.__title__)
        assert is_non_empty_string(icecream.__version__)
        assert is_non_empty_string(icecream.__license__)
        assert is_non_empty_string(icecream.__author__)
        assert is_non_empty_string(icecream.__contact__)
        assert is_non_empty_string(icecream.__description__)
        assert is_non_empty_string(icecream.__url__)

    def test_without_args(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic()
        assert line_is_context_and_time(err.getvalue())

    def test_as_argument(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            noop(ic(a), ic(b))
        pairs = parse_output_into_pairs(out, err, 2)
        assert pairs[0][0] == ('a', '1') and pairs[1][0] == ('b', '2')

        with disable_coloring(), capture_standard_streams() as (out, err):
            dic = {1: ic(a)}  # noqa
            lst = [ic(b), ic()]  # noqa
        pairs = parse_output_into_pairs(out, err, 3)
        assert pairs[0][0] == ('a', '1')
        assert pairs[1][0] == ('b', '2')
        assert line_is_context_and_time(err.getvalue().splitlines()[-1])

    def test_single_argument(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(a)
        assert parse_output_into_pairs(out, err, 1)[0][0] == ('a', '1')

    def test_multiple_arguments(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(a, b)
        pairs = parse_output_into_pairs(out, err, 1)[0]
        assert pairs == [('a', '1'), ('b', '2')]

    def test_nested_multiline(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(
                )
        assert line_is_context_and_time(err.getvalue())

        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(a,
               'foo')
        pairs = parse_output_into_pairs(out, err, 1)[0]
        assert pairs == [('a',  '1'), (None, "'foo'")]

        with disable_coloring(), capture_standard_streams() as (out, err):
            noop(noop(noop({1: ic(
                noop())})))
        assert parse_output_into_pairs(out, err, 1)[0][0] == ('noop()', 'None')

    def test_expression_arguments(self):
        class klass():
            attr = 'yep'
        d = {'d': {1: 'one'}, 'k': klass}

        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(d['d'][1])
        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert pair == ("d['d'][1]", "'one'")

        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(d['k'].attr)
        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert pair == ("d['k'].attr", "'yep'")

    def test_multiple_calls_on_same_line(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(a); ic(b, c)  # noqa
        pairs = parse_output_into_pairs(out, err, 2)
        assert pairs[0][0] == ('a', '1')
        assert pairs[1] == [('b', '2'), ('c', '3')]

    def test_call_surrounded_by_expressions(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            noop(); ic(a); noop()  # noqa
        assert parse_output_into_pairs(out, err, 1)[0][0] == ('a', '1')

    def test_comments(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            """Comment."""; ic(); # Comment.  # noqa
        assert line_is_context_and_time(err.getvalue())

    def test_method_arguments(self):
        class Foo:
            def foo(self):
                return 'foo'
        f = Foo()
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(f.foo())
        assert parse_output_into_pairs(out, err, 1)[0][0] == ('f.foo()', "'foo'")

    def test_complicated(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            noop(); ic(); noop(); ic(a,  # noqa
                                     b, noop.__class__.__name__,  # noqa
                                         noop ()); noop()  # noqa
        pairs = parse_output_into_pairs(out, err, 2)
        assert line_is_context_and_time(err.getvalue().splitlines()[0])
        assert pairs[1] == [
            ('a', '1'), ('b', '2'), ('noop.__class__.__name__', "'function'"),
            ('noop ()', 'None')]

    def test_return_value(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            assert ic() is None
            assert ic(1) == 1
            assert ic(1, 2, 3) == (1, 2, 3)

    def test_different_name(self):
        from icecream import ic as foo
        with disable_coloring(), capture_standard_streams() as (out, err):
            foo()
        assert line_is_context_and_time(err.getvalue())

        newname = foo
        with disable_coloring(), capture_standard_streams() as (out, err):
            newname(a)
        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert pair == ('a', '1')

    def test_prefix_configuration(self):
        prefix = 'lolsup '
        with configure_icecream_output(prefix, stderrPrint):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(a)
        pair = parse_output_into_pairs(out, err, 1, prefix=prefix)[0][0]
        assert pair == ('a', '1')

        def prefix_function():
            return 'lolsup '
        with configure_icecream_output(prefix=prefix_function):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(b)
        pair = parse_output_into_pairs(out, err, 1, prefix=prefix_function())[0][0]
        assert pair == ('b', '2')

    def test_output_function(self):
        lst = []

        def append_to(s):
            lst.append(s)

        with configure_icecream_output(ic.prefix, append_to):
            with capture_standard_streams() as (out, err):
                ic(a)
        assert not out.getvalue() and not err.getvalue()

        with configure_icecream_output(outputFunction=append_to):
            with capture_standard_streams() as (out, err):
                ic(b)
        assert not out.getvalue() and not err.getvalue()

        pairs = parse_output_into_pairs(out, '\n'.join(lst), 2)
        assert pairs == [[('a', '1')], [('b', '2')]]

    def test_enable_disable(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            assert ic(a) == 1
            assert ic.enabled

            ic.disable()
            assert not ic.enabled
            assert ic(b) == 2

            ic.enable()
            assert ic.enabled
            assert ic(c) == 3

        pairs = parse_output_into_pairs(out, err, 2)
        assert pairs == [[('a', '1')], [('c', '3')]]

    def test_arg_to_string_function(self):
        def hello(obj):
            return 'zwei'

        with configure_icecream_output(argToStringFunction=hello):
            with disable_coloring(), capture_standard_streams() as (out, err):
                eins = 'ein'
                ic(eins)
        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert pair == ('eins', 'zwei')

    def test_singledispatch_argument_to_string(self):
        def argument_to_string_tuple(obj):
            return "Dispatching tuple!"

        # Prepare input and output
        x = (1, 2)
        default_output = ic.format(x)

        # Register
        argumentToString.register(tuple, argument_to_string_tuple)
        assert tuple in argumentToString.registry
        assert str.endswith(ic.format(x), argument_to_string_tuple(x))

        # Unregister
        argumentToString.unregister(tuple)
        assert tuple not in argumentToString.registry
        assert ic.format(x) == default_output

    def test_single_argument_long_line_not_wrapped(self):
        # A single long line with one argument is not line wrapped.
        longStr = '*' * (ic.lineWrapWidth + 1)
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(longStr)
        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert len(err.getvalue()) > ic.lineWrapWidth
        assert pair == ('longStr', ic.argToStringFunction(longStr))

    def test_multiple_arguments_long_line_wrapped(self):
        # A single long line with multiple variables is line wrapped.
        val = '*' * int(ic.lineWrapWidth / 4)
        valStr = ic.argToStringFunction(val)

        v1 = v2 = v3 = v4 = val
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(v1, v2, v3, v4)

        pairs = parse_output_into_pairs(out, err, 4)
        assert pairs == [[(k, valStr)] for k in ['v1', 'v2', 'v3', 'v4']]

        lines = err.getvalue().splitlines()
        assert (
            lines[0].startswith(ic.prefix) and
            lines[1].startswith(' ' * len(ic.prefix)) and
            lines[2].startswith(' ' * len(ic.prefix)) and
            lines[3].startswith(' ' * len(ic.prefix)))

    def test_multiline_value_wrapped(self):
        # Multiline values are line wrapped.
        multilineStr = 'line1\nline2'
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(multilineStr)
        pair = parse_output_into_pairs(out, err, 2)[0][0]
        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))

    def test_include_context_single_line(self):
        i = 3
        with configure_icecream_output(includeContext=True):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(i)

        pair = parse_output_into_pairs(out, err, 1)[0][0]
        assert pair == ('i', '3')

    def test_context_abs_path_single_line(self):
        i = 3
        with configure_icecream_output(includeContext=True, contextAbsPath=True):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(i)
        # Output with absolute path can easily exceed line width, so no assert line num here.
        pairs = parse_output_into_pairs(out, err, 0)
        assert [('i', '3')] in pairs

    def test_values(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            # Test both 'asdf' and "asdf"; see
            # https://github.com/gruns/icecream/issues/53.
            ic(3, 'asdf', "asdf")

        pairs = parse_output_into_pairs(out, err, 1)
        assert pairs == [[(None, '3'), (None, "'asdf'"), (None, "'asdf'")]]

    def test_include_context_multi_line(self):
        multilineStr = 'line1\nline2'
        with configure_icecream_output(includeContext=True):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(multilineStr)

        firstLine = err.getvalue().splitlines()[0]
        assert line_is_context(firstLine)

        pair = parse_output_into_pairs(out, err, 3)[1][0]
        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))

    def test_context_abs_path_multi_line(self):
        multilineStr = 'line1\nline2'
        with configure_icecream_output(includeContext=True, contextAbsPath=True):
            with disable_coloring(), capture_standard_streams() as (out, err):
                ic(multilineStr)

        firstLine = err.getvalue().splitlines()[0]
        assert line_is_abs_path_context(firstLine)

        pair = parse_output_into_pairs(out, err, 3)[1][0]
        assert pair == ('multilineStr', ic.argToStringFunction(multilineStr))

    def test_format(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            """comment"""; noop(); ic(  # noqa
                'sup'); noop()  # noqa
        """comment"""; noop(); s = ic.format(  # noqa
            'sup'); noop()  # noqa
        assert s == err.getvalue().rstrip()

    def test_multiline_invocation_with_comments(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(  # Comment.

                a,  # Comment.

                # Comment.

                b,  # Comment.

                )  # Comment.

        pairs = parse_output_into_pairs(out, err, 1)[0]
        assert pairs == [('a', '1'), ('b', '2')]

    def test_no_source_available_prints_values(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            with warnings.catch_warnings():
                # we ignore the warning so that it doesn't interfere
                # with parsing ic's output
                warnings.simplefilter("ignore")
                eval('ic(a, b)')
                pairs = parse_output_into_pairs(out, err, 1)
                self.assertEqual(pairs, [[(None, '1'), (None, "2")]])

    def test_no_source_available_prints_multiline(self):
        """
        This tests for a bug which caused only multiline prints to fail.
        """
        multilineStr = 'line1\nline2'
        with disable_coloring(), capture_standard_streams() as (out, err):
            with warnings.catch_warnings():
                # we ignore the warning so that it doesn't interfere
                # with parsing ic's output
                warnings.simplefilter("ignore")
                eval('ic(multilineStr)')
                pair = parse_output_into_pairs(out, err, 2)[0][0]
                self.assertEqual(pair, (None, ic.argToStringFunction(multilineStr)))

    def test_no_source_available_issues_exactly_one_warning(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            with warnings.catch_warnings(record=True) as all_warnings:
                eval('ic(a)')
                eval('ic(b)')
                assert len(all_warnings) == 1
                warning = all_warnings[-1]
                assert NO_SOURCE_AVAILABLE_WARNING_MESSAGE in str(warning.message)

    def test_single_tuple_argument(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic((a, b))

        pair = parse_output_into_pairs(out, err, 1)[0][0]
        self.assertEqual(pair, ('(a, b)', '(1, 2)'))

    def test_flat_medium_list_prints_on_one_line(self):
        """Flat medium-sized lists should not be split one item per line."""
        data = [1, 1, 1, 1, 1, 1, 1, 1,
                0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0,
                1, 1, 1, 1, 1,
                0, 1, 0, 1, 0, 1, 0]

        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(data)

        # The whole ic() call should fit on a single line.
        self.assertEqual(len(err.getvalue().strip().splitlines()), 1)

    def test_multiline_container_args(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic((a,
                b))
            ic([a,
                b])
            ic((a,
                b),
               [list(range(15)),
                list(range(15))])

        self.assertEqual(err.getvalue().strip(), """
ic| (a,
     b): (1, 2)
ic| [a,
     b]: [1, 2]
ic| (a,
     b): (1, 2)
    [list(range(15)),
     list(range(15))]: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
                        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]
        """.strip())

        with disable_coloring(), capture_standard_streams() as (out, err):
            with configure_icecream_output(includeContext=True):
                ic((a,
                    b),
                   [list(range(15)),
                    list(range(15))])

        lines = err.getvalue().strip().splitlines()
        self.assertRegex(
            lines[0],
            r'ic\| test_icecream.py:\d+ in test_multiline_container_args\(\)',
        )
        self.assertEqual('\n'.join(lines[1:]), """\
    (a,
     b): (1, 2)
    [list(range(15)),
     list(range(15))]: [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
                        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]]""")

    def test_multiple_tuple_arguments(self):
        with disable_coloring(), capture_standard_streams() as (out, err):
            ic((a, b), (b, a), a, b)

        pair = parse_output_into_pairs(out, err, 1)[0]
        self.assertEqual(pair, [
            ('(a, b)', '(1, 2)'), ('(b, a)', '(2, 1)'), ('a', '1'), ('b', '2')])

    def test_coloring(self):
        with capture_standard_streams() as (out, err):
            ic({1: 'str'})  # Output should be colored with ANSI control codes.

        assert has_ansi_escape_codes(err.getvalue())

    def test_configure_output_with_no_parameters(self):
        with self.assertRaises(TypeError):
            ic.configureOutput()

    def test_multiline_strings_output(self):

        test1 = "A\\veryvery\\long\\path\\to\\no\\even\\longer\\HelloWorld _01_Heritisfinallythe file.file"
        test2 = r"A\veryvery\long\path\to\no\even\longer\HelloWorld _01_Heritisfinallythe file.file"
        test3 = "line\nline"

        with disable_coloring(), capture_standard_streams() as (_, err):
            ic(test1)
            curr_res = err.getvalue().strip()
            expected = r"ic| test1: 'A\\veryvery\\long\\path\\to\\no\\even\\longer\\HelloWorld _01_Heritisfinallythe file.file'"
            self.assertEqual(curr_res, expected)
            del curr_res, expected

        with disable_coloring(), capture_standard_streams() as (_, err):
            ic(test2)
            curr_res = err.getvalue().strip()
            # expected = r"ic| test2: 'A\\veryvery\\long\\path\\to\\no\\even\\longer\\HelloWorld _01_Heritisfinallythe file.file'"
            expected = r"ic| test2: 'A\\veryvery\\long\\path\\to\\no\\even\\longer\\HelloWorld _01_Heritisfinallythe file.file'"
            self.assertEqual(curr_res, expected)
            del curr_res, expected

        with disable_coloring(), capture_standard_streams() as (_, err):
            ic(test3)
            curr_res = err.getvalue().strip()
            expected = r"""ic| test3: '''line
            line'''"""
            self.assertEqual(curr_res, expected)
            del curr_res, expected

    def test_sympy_dict_keys_do_not_crash(self):
        """Regression: ic() must not raise when dict keys are SymPy symbols."""

        try:
            import sympy as sp
        except Exception:
            self.skipTest("sympy not installed")

        x, y = sp.symbols("x y")
        d = {x: "hello", y: "world"}

        with disable_coloring(), capture_standard_streams() as (out, err):
            # If the bug regresses, this line raises TypeError.
            ic(d)

        s = err.getvalue().strip()
        # Basic sanity checks without assuming exact formatting or ordering.
        self.assertIn("ic|", s)
        self.assertIn("hello", s)
        self.assertIn("world", s)

    def test_sympy_solve_result_does_not_crash(self):
        """Regression: ic() must handle SymPy solve() outputs."""

        try:
            import sympy as sp
        except Exception:
            self.skipTest("sympy not installed")

        x, y = sp.symbols("x y")
        res = sp.solve([x + 2, y - 2])   # list/dict of symbolic items

        with disable_coloring(), capture_standard_streams() as (out, err):
            ic(res)

        s = err.getvalue()
        self.assertIn("ic|", s)
        # Don’t assert exact text; just ensure something printed.
        self.assertTrue(len(s) > 0)


================================================
FILE: tests/test_install.py
================================================
# -*- coding: utf-8 -*-

#
# IceCream - Never use print() to debug again
#
# Ansgar Grunseid
# grunseid.com
# grunseid@gmail.com
#
# License: MIT
#

import unittest

import icecream
from tests.test_icecream import (
    disable_coloring, capture_standard_streams, parse_output_into_pairs)

from tests.install_test_import import runMe


class TestIceCreamInstall(unittest.TestCase):
    def test_install(self):
        icecream.install()
        with disable_coloring(), capture_standard_streams() as (out, err):
            runMe()
        assert parse_output_into_pairs(out, err, 1)[0][0] == ('x', '3')
        icecream.uninstall()  # Clean up builtins.

    def test_uninstall(self):
        try:
            icecream.uninstall()
        except AttributeError:  # Already uninstalled.
            pass

        # NameError: global name 'ic' is not defined.
        with self.assertRaises(NameError):
            runMe()


================================================
FILE: tox.ini
================================================
[tox]
envlist = py39, py310, py311, py312, py313, py314, pypy3

[testenv]
description =
    run unittest
commands =
    python -m unittest
deps =
    sympy>=1.12

[testenv:mypy]
basepython = python3.9
deps =
   mypy==1.7.1
   types-pygments
   types-colorama
commands =
  mypy icecream
Download .txt
gitextract__hqhlpvx/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── changelog.txt
├── failures-to-investigate/
│   ├── freshsales.py
│   ├── freshsales2.py
│   └── freshsales3.py
├── icecream/
│   ├── __init__.py
│   ├── __version__.py
│   ├── builtins.py
│   ├── coloring.py
│   ├── icecream.py
│   └── py.typed
├── pyproject.toml
├── setup.cfg
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── install_test_import.py
│   ├── test_icecream.py
│   └── test_install.py
└── tox.ini
Download .txt
SYMBOL INDEX (191 symbols across 10 files)

FILE: failures-to-investigate/freshsales.py
  function splitName (line 45) | def splitName(name):
  function lookupFullContact (line 58) | def lookupFullContact(contact):
  function findFirstContactWithEmail (line 67) | def findFirstContactWithEmail(emailAddr):
  function findFirstCompanyWithWebsite (line 69) | def findFirstCompanyWithWebsite(websiteUrl):
  function _findFirstEntityOf (line 71) | def _findFirstEntityOf(entityType, query, queryValue):
  function createNote (line 87) | def createNote(entityType, entityId, message):
  function createLead (line 103) | def createLead(data):
  function createContact (line 106) | def createContact(data):
  function createCompany (line 111) | def createCompany(data):
  function _createEntity (line 114) | def _createEntity(entityType, data):
  function updateLead (line 126) | def updateLead(leadId, data):
  function updateContact (line 129) | def updateContact(contactId, data):
  function updateCompany (line 132) | def updateCompany(companyId, data):
  function _updateEntity (line 135) | def _updateEntity(entityType, entityId, data):
  function lookupContactsInView (line 148) | def lookupContactsInView(viewId):
  function _lookupEntitiesInView (line 151) | def _lookupEntitiesInView(entityType, viewId):
  function unsubscribeContact (line 170) | def unsubscribeContact(contact, reasons):
  function optContactIn (line 188) | def optContactIn(contact):
  function createAndOrAssociateCompanyWithContact (line 195) | def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
  function upgradeContactWhoSubmittedSplashPage (line 227) | def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
  function noteContactSubmittedPepSplashPage (line 243) | def noteContactSubmittedPepSplashPage(contact, websiteUrl):
  function createCrawledIndieHackersContact (line 261) | def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteDa...
  function _createCrawledContact (line 265) | def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, not...
  function createSplashPageLead (line 290) | def createSplashPageLead(name, emailAddr, websiteUrl):
  function createPepSplashPageLead (line 314) | def createPepSplashPageLead(emailAddr, websiteUrl):
  function noteACustomersFirstWidgetReport (line 340) | def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
  function handleWordPressPluginInstall (line 359) | def handleWordPressPluginInstall(emailAddr, websiteUrl):
  function handleWordPressPluginCreatedArcAccount (line 394) | def handleWordPressPluginCreatedArcAccount(emailAddr):
  function handleWordPressPluginUninstall (line 409) | def handleWordPressPluginUninstall(emailAddr):

FILE: failures-to-investigate/freshsales2.py
  function splitName (line 46) | def splitName(name):
  function lookupFullContact (line 59) | def lookupFullContact(contact):
  function findFirstContactWithEmail (line 68) | def findFirstContactWithEmail(emailAddr):
  function findFirstCompanyWithWebsite (line 71) | def findFirstCompanyWithWebsite(websiteUrl):
  function _findEntitiesWith (line 91) | def _findEntitiesWith(entityType, query, queryValue):
  function createNote (line 100) | def createNote(entityType, entityId, message):
  function createLead (line 116) | def createLead(data):
  function createContact (line 119) | def createContact(data):
  function createCompany (line 124) | def createCompany(data):
  function _createEntity (line 127) | def _createEntity(entityType, data):
  function updateLead (line 139) | def updateLead(leadId, data):
  function updateContact (line 142) | def updateContact(contactId, data):
  function updateCompany (line 145) | def updateCompany(companyId, data):
  function _updateEntity (line 148) | def _updateEntity(entityType, entityId, data):
  function lookupContactsInView (line 161) | def lookupContactsInView(viewId):
  function _lookupEntitiesInView (line 164) | def _lookupEntitiesInView(entityType, viewId):
  function unsubscribeContact (line 183) | def unsubscribeContact(contact, reasons):
  function optContactIn (line 201) | def optContactIn(contact):
  function createAndOrAssociateCompanyWithContact (line 208) | def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
  function upgradeContactWhoSubmittedSplashPage (line 240) | def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
  function noteContactSubmittedPepSplashPage (line 256) | def noteContactSubmittedPepSplashPage(contact, websiteUrl):
  function createCrawledIndieHackersContact (line 274) | def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteDa...
  function _createCrawledContact (line 278) | def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, not...
  function createSplashPageLead (line 303) | def createSplashPageLead(name, emailAddr, websiteUrl):
  function createPepSplashPageLead (line 327) | def createPepSplashPageLead(emailAddr, websiteUrl):
  function noteACustomersFirstWidgetReport (line 353) | def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
  function handleWordPressPluginInstall (line 372) | def handleWordPressPluginInstall(emailAddr, websiteUrl):
  function handleWordPressPluginCreatedArcAccount (line 407) | def handleWordPressPluginCreatedArcAccount(emailAddr):
  function handleWordPressPluginUninstall (line 422) | def handleWordPressPluginUninstall(emailAddr):

FILE: failures-to-investigate/freshsales3.py
  function splitName (line 46) | def splitName(name):
  function lookupFullContact (line 59) | def lookupFullContact(contact):
  function findFirstContactWithEmail (line 68) | def findFirstContactWithEmail(emailAddr):
  function findFirstCompanyWithWebsite (line 71) | def findFirstCompanyWithWebsite(websiteUrl):
  function _findEntitiesWith (line 98) | def _findEntitiesWith(entityType, query, queryValue):
  function createNote (line 107) | def createNote(entityType, entityId, message):
  function createLead (line 123) | def createLead(data):
  function createContact (line 126) | def createContact(data):
  function createCompany (line 131) | def createCompany(data):
  function _createEntity (line 134) | def _createEntity(entityType, data):
  function updateLead (line 146) | def updateLead(leadId, data):
  function updateContact (line 149) | def updateContact(contactId, data):
  function updateCompany (line 152) | def updateCompany(companyId, data):
  function _updateEntity (line 155) | def _updateEntity(entityType, entityId, data):
  function lookupContactsInView (line 168) | def lookupContactsInView(viewId):
  function _lookupEntitiesInView (line 171) | def _lookupEntitiesInView(entityType, viewId):
  function unsubscribeContact (line 190) | def unsubscribeContact(contact, reasons):
  function optContactIn (line 208) | def optContactIn(contact):
  function createAndOrAssociateCompanyWithContact (line 215) | def createAndOrAssociateCompanyWithContact(websiteUrl, contact):
  function upgradeContactWhoSubmittedSplashPage (line 247) | def upgradeContactWhoSubmittedSplashPage(contact, websiteUrl):
  function noteContactSubmittedPepSplashPage (line 263) | def noteContactSubmittedPepSplashPage(contact, websiteUrl):
  function createCrawledIndieHackersContact (line 281) | def createCrawledIndieHackersContact(name, emailAddr, websiteUrl, noteDa...
  function _createCrawledContact (line 285) | def _createCrawledContact(name, emailAddr, websiteUrl, leadSourceId, not...
  function createSplashPageLead (line 310) | def createSplashPageLead(name, emailAddr, websiteUrl):
  function createPepSplashPageLead (line 334) | def createPepSplashPageLead(emailAddr, websiteUrl):
  function noteACustomersFirstWidgetReport (line 360) | def noteACustomersFirstWidgetReport(emailAddr, seenOnUrl):
  function handleWordPressPluginInstall (line 379) | def handleWordPressPluginInstall(emailAddr, websiteUrl):
  function handleWordPressPluginCreatedArcAccount (line 414) | def handleWordPressPluginCreatedArcAccount(emailAddr):
  function handleWordPressPluginUninstall (line 429) | def handleWordPressPluginUninstall(emailAddr):

FILE: icecream/builtins.py
  function install (line 19) | def install(
  function uninstall (line 28) | def uninstall(ic: str = 'ic') -> None:

FILE: icecream/coloring.py
  class SolarizedDark (line 20) | class SolarizedDark(Style):

FILE: icecream/icecream.py
  class Sentinel (line 41) | class Sentinel(enum.Enum):
  function bindStaticVariable (line 45) | def bindStaticVariable(name: str, value: Any) -> Callable:
  function colorize (line 54) | def colorize(s: str) -> str:
  function supportTerminalColorsInWindows (line 64) | def supportTerminalColorsInWindows() -> Generator:
  function stderrPrint (line 75) | def stderrPrint(*args: object) -> None:
  function isLiteral (line 79) | def isLiteral(s: str) -> bool:
  function colorizedStderrPrint (line 87) | def colorizedStderrPrint(s: str) -> None:
  function colorizedStdoutPrint (line 93) | def colorizedStdoutPrint(s: str) -> None:
  function safe_pformat (line 99) | def safe_pformat(obj: object, *args: Any, **kwargs: Any) -> str:
  function callOrValue (line 179) | def callOrValue(obj: object) -> object:
  class Source (line 183) | class Source(executing.Source):
    method get_text_with_indentation (line 184) | def get_text_with_indentation(self, node: ast.expr) -> str:
  function prefixLines (line 193) | def prefixLines(prefix: str, s: str, startAtLine: int = 0) -> List[str]:
  function prefixFirstLineIndentRemaining (line 202) | def prefixFirstLineIndentRemaining(prefix: str, s: str) -> List[str]:
  function formatPair (line 209) | def formatPair(prefix: str, arg: Union[str, Sentinel], value: str) -> str:
  class _SingleDispatchCallable (line 227) | class _SingleDispatchCallable:
    method __call__ (line 228) | def __call__(self, *_: object) -> str:
  function singledispatch (line 234) | def singledispatch(func: Callable) -> _SingleDispatchCallable:
  function argumentToString (line 253) | def argumentToString(obj: object) -> str:
  function _ (line 260) | def _(obj: str) -> str:
  class IceCreamDebugger (line 267) | class IceCreamDebugger:
    method __init__ (line 272) | def __init__(self, prefix: Union[str, Callable[[], str]] =DEFAULT_PREFIX,
    method __call__ (line 283) | def __call__(self, *args: object) -> object:
    method format (line 299) | def format(self, *args: object) -> str:
    method _format (line 306) | def _format(self, callFrame: FrameType, *args: object) -> str:
    method _formatArgs (line 321) | def _formatArgs(self, callFrame: FrameType, prefix: str, context: str,...
    method _constructArgumentOutput (line 340) | def _constructArgumentOutput(self, prefix: str, context: str, pairs: S...
    method _formatContext (line 401) | def _formatContext(self, callFrame: FrameType) -> str:
    method _formatTime (line 410) | def _formatTime(self) -> str:
    method _getContext (line 415) | def _getContext(self, callFrame: FrameType) -> Tuple[str, int, str]:
    method enable (line 423) | def enable(self) -> None:
    method disable (line 426) | def disable(self) -> None:
    method use_stdout (line 429) | def use_stdout(self) -> None:
    method use_stderr (line 432) | def use_stderr(self) -> None:
    method configureOutput (line 435) | def configureOutput(

FILE: setup.py
  class Publish (line 26) | class Publish(Command):
    method initialize_options (line 30) | def initialize_options(self) -> None:
    method finalize_options (line 33) | def finalize_options(self) -> None:
    method run (line 36) | def run(self) -> None:
  class RunTests (line 46) | class RunTests(TestCommand):
    method run_tests (line 60) | def run_tests(self) -> None:

FILE: tests/install_test_import.py
  function runMe (line 13) | def runMe():

FILE: tests/test_icecream.py
  function noop (line 34) | def noop(*args, **kwargs):
  function has_ansi_escape_codes (line 38) | def has_ansi_escape_codes(s):
  class FakeTeletypeBuffer (line 43) | class FakeTeletypeBuffer(StringIO):
    method isatty (line 48) | def isatty(self):
  function disable_coloring (line 53) | def disable_coloring():
  function configure_icecream_output (line 62) | def configure_icecream_output(prefix=None, outputFunction=None,
  function capture_standard_streams (line 90) | def capture_standard_streams():
  function strip_prefix (line 104) | def strip_prefix(line):
  function line_is_context_and_time (line 110) | def line_is_context_and_time(line):
  function line_is_context (line 120) | def line_is_context(line):
  function line_is_abs_path_context (line 132) | def line_is_abs_path_context(line):
  function line_after_context (line 144) | def line_after_context(line, prefix):
  function parse_output_into_pairs (line 155) | def parse_output_into_pairs(out, err, assert_num_lines,
  class TestIceCream (line 196) | class TestIceCream(unittest.TestCase):
    method setUp (line 197) | def setUp(self):
    method test_metadata (line 200) | def test_metadata(self):
    method test_without_args (line 211) | def test_without_args(self):
    method test_as_argument (line 216) | def test_as_argument(self):
    method test_single_argument (line 230) | def test_single_argument(self):
    method test_multiple_arguments (line 235) | def test_multiple_arguments(self):
    method test_nested_multiline (line 241) | def test_nested_multiline(self):
    method test_expression_arguments (line 258) | def test_expression_arguments(self):
    method test_multiple_calls_on_same_line (line 273) | def test_multiple_calls_on_same_line(self):
    method test_call_surrounded_by_expressions (line 280) | def test_call_surrounded_by_expressions(self):
    method test_comments (line 285) | def test_comments(self):
    method test_method_arguments (line 290) | def test_method_arguments(self):
    method test_complicated (line 299) | def test_complicated(self):
    method test_return_value (line 310) | def test_return_value(self):
    method test_different_name (line 316) | def test_different_name(self):
    method test_prefix_configuration (line 328) | def test_prefix_configuration(self):
    method test_output_function (line 344) | def test_output_function(self):
    method test_enable_disable (line 363) | def test_enable_disable(self):
    method test_arg_to_string_function (line 379) | def test_arg_to_string_function(self):
    method test_singledispatch_argument_to_string (line 390) | def test_singledispatch_argument_to_string(self):
    method test_single_argument_long_line_not_wrapped (line 408) | def test_single_argument_long_line_not_wrapped(self):
    method test_multiple_arguments_long_line_wrapped (line 417) | def test_multiple_arguments_long_line_wrapped(self):
    method test_multiline_value_wrapped (line 436) | def test_multiline_value_wrapped(self):
    method test_include_context_single_line (line 444) | def test_include_context_single_line(self):
    method test_context_abs_path_single_line (line 453) | def test_context_abs_path_single_line(self):
    method test_values (line 462) | def test_values(self):
    method test_include_context_multi_line (line 471) | def test_include_context_multi_line(self):
    method test_context_abs_path_multi_line (line 483) | def test_context_abs_path_multi_line(self):
    method test_format (line 495) | def test_format(self):
    method test_multiline_invocation_with_comments (line 503) | def test_multiline_invocation_with_comments(self):
    method test_no_source_available_prints_values (line 518) | def test_no_source_available_prints_values(self):
    method test_no_source_available_prints_multiline (line 528) | def test_no_source_available_prints_multiline(self):
    method test_no_source_available_issues_exactly_one_warning (line 542) | def test_no_source_available_issues_exactly_one_warning(self):
    method test_single_tuple_argument (line 551) | def test_single_tuple_argument(self):
    method test_flat_medium_list_prints_on_one_line (line 558) | def test_flat_medium_list_prints_on_one_line(self):
    method test_multiline_container_args (line 572) | def test_multiline_container_args(self):
    method test_multiple_tuple_arguments (line 614) | def test_multiple_tuple_arguments(self):
    method test_coloring (line 622) | def test_coloring(self):
    method test_configure_output_with_no_parameters (line 628) | def test_configure_output_with_no_parameters(self):
    method test_multiline_strings_output (line 632) | def test_multiline_strings_output(self):
    method test_sympy_dict_keys_do_not_crash (line 661) | def test_sympy_dict_keys_do_not_crash(self):
    method test_sympy_solve_result_does_not_crash (line 682) | def test_sympy_solve_result_does_not_crash(self):

FILE: tests/test_install.py
  class TestIceCreamInstall (line 22) | class TestIceCreamInstall(unittest.TestCase):
    method test_install (line 23) | def test_install(self):
    method test_uninstall (line 30) | def test_uninstall(self):
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 118,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1042,
    "preview": "name: CI\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\njobs:\n  build:\n    runs-o"
  },
  {
    "path": ".gitignore",
    "chars": 84,
    "preview": "*~\n.#*\n\\#*\n.tox\ndist/\n.eggs/\nbuild/\n*.pyc\n*.pyo\n*.egg\n*.egg-info\n.aider*\nplayground\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1054,
    "preview": "Copyright 2018 Ansgar Grunseid\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis sof"
  },
  {
    "path": "MANIFEST.in",
    "chars": 42,
    "preview": "include LICENSE.txt README.md\nprune tests\n"
  },
  {
    "path": "README.md",
    "chars": 10475,
    "preview": "<h1 align=\"center\">\n  <img src=\"logo.svg\" width=\"220px\" height=\"370px\" alt=\"IceCream\">\n</h1>\n\n<p align=\"center\">\n  <a hr"
  },
  {
    "path": "changelog.txt",
    "chars": 6480,
    "preview": "================================================================================\nv2.1.10\n==============================="
  },
  {
    "path": "failures-to-investigate/freshsales.py",
    "chars": 13007,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, j"
  },
  {
    "path": "failures-to-investigate/freshsales2.py",
    "chars": 13767,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, j"
  },
  {
    "path": "failures-to-investigate/freshsales3.py",
    "chars": 14018,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n# Copyright _!_\n#\n# License _!_\n\nfrom os.path import abspath, dirname, j"
  },
  {
    "path": "icecream/__init__.py",
    "chars": 388,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "icecream/__version__.py",
    "chars": 486,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "icecream/builtins.py",
    "chars": 510,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "icecream/coloring.py",
    "chars": 3623,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "icecream/icecream.py",
    "chars": 16043,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# gru"
  },
  {
    "path": "icecream/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pyproject.toml",
    "chars": 114,
    "preview": "[tool.mypy]\nshow_error_codes=true\ndisallow_untyped_defs=true\ndisallow_untyped_calls=true\nwarn_redundant_casts=true"
  },
  {
    "path": "setup.cfg",
    "chars": 39,
    "preview": "[metadata]\nlicense_files = LICENSE.txt\n"
  },
  {
    "path": "setup.py",
    "chars": 3325,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# gr"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/install_test_import.py",
    "chars": 182,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "tests/test_icecream.py",
    "chars": 25093,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "tests/test_install.py",
    "chars": 922,
    "preview": "# -*- coding: utf-8 -*-\n\n#\n# IceCream - Never use print() to debug again\n#\n# Ansgar Grunseid\n# grunseid.com\n# grunseid@g"
  },
  {
    "path": "tox.ini",
    "chars": 286,
    "preview": "[tox]\nenvlist = py39, py310, py311, py312, py313, py314, pypy3\n\n[testenv]\ndescription =\n    run unittest\ncommands =\n    "
  }
]

About this extraction

This page contains the full source code of the gruns/icecream GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (108.5 KB), approximately 28.3k tokens, and a symbol index with 191 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!