pax_global_header00006660000000000000000000000064142662144610014520gustar00rootroot0000000000000052 comment=cbd6da2fa02d480cd32df1e9e526c360318843f0 icecream-2.1.3/000077500000000000000000000000001426621446100132735ustar00rootroot00000000000000icecream-2.1.3/.gitignore000066400000000000000000000001011426621446100152530ustar00rootroot00000000000000*~ .#* \#* .tox dist/ .eggs/ build/ *.pyc *.pyo *.egg *.egg-info icecream-2.1.3/.travis.yml000066400000000000000000000007171426621446100154110ustar00rootroot00000000000000language: python sudo: false matrix: include: - python: 2.7 env: TOXENV=py27 - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - python: 3.7 env: TOXENV=py37 - python: 3.8 env: TOXENV=py38 - python: 3.9 env: TOXENV=py39 - python: pypy env: TOXENV=pypy - python: pypy3 env: TOXENV=pypy3 install: travis_retry pip install tox script: tox notifications: email: false icecream-2.1.3/LICENSE.txt000066400000000000000000000020361426621446100151170ustar00rootroot00000000000000Copyright 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.icecream-2.1.3/MANIFEST.in000066400000000000000000000000361426621446100150300ustar00rootroot00000000000000include LICENSE.txt README.md icecream-2.1.3/README.md000066400000000000000000000227121426621446100145560ustar00rootroot00000000000000

icecream

### 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 expressions/variable names and their values. 2. It's 40% faster to type. 3. Data structures are 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 2, Python 3, PyPy2, and PyPy3. ### 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) >>> >>> 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: , numpy.ndarray: }) >>> >>> # 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. ### 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) 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. icecream-2.1.3/changelog.txt000066400000000000000000000070671426621446100157750ustar00rootroot00000000000000================================================================================ 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. icecream-2.1.3/failures-to-investigate/000077500000000000000000000000001426621446100200455ustar00rootroot00000000000000icecream-2.1.3/failures-to-investigate/freshsales.py000066400000000000000000000313171426621446100225630ustar00rootroot00000000000000#!/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 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')) icecream-2.1.3/failures-to-investigate/freshsales2.py000066400000000000000000000327071426621446100226510ustar00rootroot00000000000000#!/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 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')) icecream-2.1.3/failures-to-investigate/freshsales3.py000066400000000000000000000333021426621446100226420ustar00rootroot00000000000000#!/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 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')) icecream-2.1.3/icecream/000077500000000000000000000000001426621446100150435ustar00rootroot00000000000000icecream-2.1.3/icecream/__init__.py000066400000000000000000000006601426621446100171560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # IceCream - Never use print() to debug again # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: MIT # from os.path import dirname, join as pjoin 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())) icecream-2.1.3/icecream/__version__.py000066400000000000000000000007451426621446100177040ustar00rootroot00000000000000# -*- 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.3' __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.') icecream-2.1.3/icecream/builtins.py000066400000000000000000000006021426621446100172440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # IceCream - Never use print() to debug again # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: MIT # import icecream try: builtins = __import__('__builtin__') except ImportError: builtins = __import__('builtins') def install(ic='ic'): setattr(builtins, ic, icecream.ic) def uninstall(ic='ic'): delattr(builtins, ic) icecream-2.1.3/icecream/coloring.py000066400000000000000000000070471426621446100172410ustar00rootroot00000000000000# -*- 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, } icecream-2.1.3/icecream/icecream.py000066400000000000000000000260031426621446100171660ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # IceCream - Never use print() to debug again # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: MIT # from __future__ import print_function import ast import inspect import pprint import sys 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 PYTHON2 = (sys.version_info[0] == 2) _absent = object() def bindStaticVariable(name, value): def decorator(fn): setattr(fn, name, value) return fn return decorator @bindStaticVariable('formatter', Terminal256Formatter(style=SolarizedDark)) @bindStaticVariable( 'lexer', PyLexer(ensurenl=False) if PYTHON2 else Py3Lexer(ensurenl=False)) def colorize(s): self = colorize return highlight(s, self.lexer, self.formatter) @contextmanager def supportTerminalColorsInWindows(): # Filter and replace ANSI escape sequences on Windows with equivalent Win32 # API calls. This code does nothing on non-Windows systems. colorama.init() yield colorama.deinit() def stderrPrint(*args): print(*args, file=sys.stderr) def isLiteral(s): try: ast.literal_eval(s) except Exception: return False return True def colorizedStderrPrint(s): colored = colorize(s) with supportTerminalColorsInWindows(): stderrPrint(colored) DEFAULT_PREFIX = 'ic| ' DEFAULT_LINE_WRAP_WIDTH = 70 # Characters. DEFAULT_CONTEXT_DELIMITER = '- ' DEFAULT_OUTPUT_FUNCTION = colorizedStderrPrint DEFAULT_ARG_TO_STRING_FUNCTION = pprint.pformat class NoSourceAvailableError(OSError): """ Raised 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. """ infoMessage = ( '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): return obj() if callable(obj) else obj class Source(executing.Source): def get_text_with_indentation(self, node): result = self.asttokens().get_text(node) if '\n' in result: result = ' ' * node.first_token.start[1] + result result = dedent(result) result = result.strip() return result def prefixLinesAfterFirst(prefix, s): lines = s.splitlines(True) for i in range(1, len(lines)): lines[i] = prefix + lines[i] return ''.join(lines) def indented_lines(prefix, string): lines = string.splitlines() return [prefix + lines[0]] + [ ' ' * len(prefix) + line for line in lines[1:] ] def format_pair(prefix, arg, value): arg_lines = indented_lines(prefix, arg) value_prefix = arg_lines[-1] + ': ' looksLikeAString = value[0] + value[-1] in ["''", '""'] if looksLikeAString: # Align the start of multiline strings. value = prefixLinesAfterFirst(' ', value) value_lines = indented_lines(value_prefix, value) lines = arg_lines[:-1] + value_lines return '\n'.join(lines) def singledispatch(func): if "singledispatch" not in dir(functools): def unsupport_py2(*args, **kwargs): raise NotImplementedError( "functools.singledispatch is missing in " + sys.version ) func.register = func.unregister = unsupport_py2 return func func = functools.singledispatch(func) # add unregister based on https://stackoverflow.com/a/25951784 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): del registry[cls] dispatch_cache.clear() func.unregister = unregister return func @singledispatch def argumentToString(obj): s = DEFAULT_ARG_TO_STRING_FUNCTION(obj) s = s.replace('\\n', '\n') # Preserve string newlines in output. return s class IceCreamDebugger: _pairDelimiter = ', ' # Used by the tests in tests/. lineWrapWidth = DEFAULT_LINE_WRAP_WIDTH contextDelimiter = DEFAULT_CONTEXT_DELIMITER def __init__(self, prefix=DEFAULT_PREFIX, outputFunction=DEFAULT_OUTPUT_FUNCTION, argToStringFunction=argumentToString, includeContext=False, contextAbsPath=False): self.enabled = True self.prefix = prefix self.includeContext = includeContext self.outputFunction = outputFunction self.argToStringFunction = argToStringFunction self.contextAbsPath = contextAbsPath def __call__(self, *args): if self.enabled: callFrame = inspect.currentframe().f_back try: out = self._format(callFrame, *args) except NoSourceAvailableError as err: prefix = callOrValue(self.prefix) out = prefix + 'Error: ' + err.infoMessage self.outputFunction(out) 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): callFrame = inspect.currentframe().f_back out = self._format(callFrame, *args) return out def _format(self, callFrame, *args): prefix = callOrValue(self.prefix) callNode = Source.executing(callFrame).node if callNode is None: raise NoSourceAvailableError() context = self._formatContext(callFrame, callNode) if not args: time = self._formatTime() out = prefix + context + time else: if not self.includeContext: context = '' out = self._formatArgs( callFrame, callNode, prefix, context, args) return out def _formatArgs(self, callFrame, callNode, prefix, context, args): source = Source.for_frame(callFrame) sanitizedArgStrs = [ source.get_text_with_indentation(arg) for arg in callNode.args] pairs = list(zip(sanitizedArgStrs, args)) out = self._constructArgumentOutput(prefix, context, pairs) return out def _constructArgumentOutput(self, prefix, context, pairs): def argPrefix(arg): return '%s: ' % arg pairs = [(arg, self.argToStringFunction(val)) for arg, val in pairs] # For cleaner output, if is a literal, eg 3, "string", b'bytes', # etc, only output the value, not the argument and the value, as the # argument and the value will be identical or nigh identical. Ex: with # ic("hello"), just output # # ic| 'hello', # # instead of # # ic| "hello": 'hello'. # pairStrs = [ val if 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] + [ format_pair(len(prefix) * ' ', arg, value) for arg, value in pairs ] # ic| multilineStr: 'line1 # line2' # # ic| a: 11111111111111111111 # b: 22222222222222222222 else: arg_lines = [ format_pair('', arg, value) for arg, value in pairs ] lines = indented_lines(prefix, '\n'.join(arg_lines)) # 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, callNode): filename, lineNumber, parentFunction = self._getContext( callFrame, callNode) if parentFunction != '': parentFunction = '%s()' % parentFunction context = '%s:%s in %s' % (filename, lineNumber, parentFunction) return context def _formatTime(self): now = datetime.now() formatted = now.strftime('%H:%M:%S.%f')[:-3] return ' at %s' % formatted def _getContext(self, callFrame, callNode): lineNumber = callNode.lineno frameInfo = inspect.getframeinfo(callFrame) parentFunction = frameInfo.function filepath = (realpath if self.contextAbsPath else basename)(frameInfo.filename) return filepath, lineNumber, parentFunction def enable(self): self.enabled = True def disable(self): self.enabled = False def configureOutput(self, prefix=_absent, outputFunction=_absent, argToStringFunction=_absent, includeContext=_absent, contextAbsPath=_absent): noParameterProvided = all( v is _absent for k,v in locals().items() if k != 'self') if noParameterProvided: raise TypeError('configureOutput() missing at least one argument') if prefix is not _absent: self.prefix = prefix if outputFunction is not _absent: self.outputFunction = outputFunction if argToStringFunction is not _absent: self.argToStringFunction = argToStringFunction if includeContext is not _absent: self.includeContext = includeContext if contextAbsPath is not _absent: self.contextAbsPath = contextAbsPath ic = IceCreamDebugger() icecream-2.1.3/logo-chocolate-370x370.png000066400000000000000000000330461426621446100176370ustar00rootroot00000000000000PNG  IHDRrr$Q gAMA a cHRMz&u0`:pQ<bKGDC pHYs a abtIME[4IDATxy?Ϸg{Po<%$f[xndl4 EQx03 30w@ LwWSUy^LOXKu&Zrt!`SZ̪>u1!K:[wr9l; 9rT!,fTUBq d̚Ot{'+6m<"͟1fĝC^BI1n> 8Y0_0BwJmsA">-lX";NhucSߺC0Dw J|'+tbq`(Kݮ;٢4_;eOsQ,/+Uzcnԝ죀T];e>f1#oz(;e>F|kԼ A" ;eDȂ[!(3XtOVSu(~H{R|Aw %sx7()(,!~,rtA`pF fU;ُEN.Н2O5^+@"'Rj 8_wp1AgA١@bzsP֜;ًENG9Eװ (S;هENj({1oOa;eT@a縺:AeA@@,lXd @5)ӝ"_~Z@D uR]CX`vͩ58u# (@NF)DtG =fS! :ݑȁDԝ" &F j6ۛNE!,ryFՐ)D_Twry܅nU])bU&;هE"gWWp7jЫ;هEuu0v ?ic>Ad0zΕ(Lԝլ;هEP7]0$7@Kh U; yȽGz" fW#V*%NDtg !n=,#>; %#}`,Uy%&BB k<[wJ/l ug(뿇_uut԰5Z8 +!Y(ש["; %Eɂ57Gp 8uaGK{&M( zp"~1]#P܅]h팢;)HЇ *#R{"#?n]LB"`껠M9RPЊ ;aY$ǔ% zՎVtE'ފR,I.^׈NEe ܤ8òsj!g ưKXX~?jy`RUYOywhyh@8k%̮"8~nt$7|vbXI<2?ʲ}s/vNgw44ԂSP7L)= ȳdѳD{.#tc|MWMи3ڍ Yu ri)Ͽ֖׉,5D9ZZ;[;@}C=tQly(m(z)?S|R:9gusǔKz YNK)<>Ɓ +ߗoAkP@i~VskJJ'^nn=w~GeX] _4ajwkzN2b,д?/[ Z[ )EUqˊT԰3h;\;G:غ6om寛#vdgeY=MSRXrꋠu9c6om:Ss./ @a 'fFu3f.r?{is l,]:_ f;H.ahOԝNam4M`LE}{w9u,r\#!r0eL-ۙXS+);!o< *p2aVu,EnedHqLF#8uh]qһ74q)ew$pCkgT)n`vͩ~0/ =]_Z^V%g %yL;SX03pCPCL(ol}Zw/c`RMD+O(;;%gU28;tcsf{}tM:dK}cۺx4Ւ0>@}(6GV8Ѕ߫( abM1t&t{~^ȇFa / !/Z.n):Ѝ:{"(/tI$zϫ;Ob FăvF4J_o.8pxȼ,qq~XP^w" e  b$I)k?{cxغKۦ(N)`q=x\wJ%Z񻵻#~|ǧDD.՝rݔ&T"foaBؙIJ 235`l-ωRq(yr$nPMK|C{qPWGu>*wѠ̮ 1d$">gaT&,$(5 "X( 1"\-9|݇e)X3 tGA7L+Ѭ&d9Qx{ʀldHQ֞g{w[J?;$vw8|T3}^czPTSJP "O+ǞNljmݽl{^i ek|`4 Dya5qkYcn  Vlݏ"ᴡ-$E ,K Oȯ=g<>o:FڶOaӇǝ+Ǟ?nی=2Zigm<@)WQIA 5+ljKG^IFeQ^ x`\[K 1pYxkpESc഑eӉx'K5uwdgL#XIP0tgnl@*vwc_oGZ0DpƨN~SP՘^1*wD)ūe̐<!Qړxq,$ȓz|a lz^݌XrQITMׯ { CpvU-ϻ|fV~uaAȓx& jFN4dOmX۲͖mLSN.wLcfʈB3\# Ӻvvk-3?XxI`'GA!XܰmQ{OuƣXܸG+O83&gJʠP\yT=b[obkCe PZtea > T,d 莐װ Q{Ə6Bswh }J^sx1yz_d?ǏE۲lGŬ~m'%{8hx t4g5K3]#8K ΑxnG?a£+%Co݁E kߦc}Jز?ۿՍ* 1K7uhbz8Һ>10*FoߏEk26floln"OyZ-CYwdE:o܍@/RWPhšofXQK{z ]yrW<[;[feRK)׾klGw"/<_~0֍]w=eeJc:a9W8̂U  !8yS=ziC3Vw2TKo./p饶 (u=:٫-Z9)y3Pp}[P8教r2|fdeEss9L3%*Gdg e<;"͎Qwт~R[{RV n"Oky r7lh5-НpՍ?E+<;G.P>ۼ θF$,jxt:'^ tgpy,e<;C.y}?q9ئf|k X :anY+>7ͲSg`D8{[ ?Hr_-o;py&.)qªFlnCy Łop2N~,V-(I7qBA80 ?d*YLx{pF1t$&D "Z ٶklæ挮Cw(Ƭ`ڋKP毶xpm?䷆BCtq"nd Z/V[x0[+6;⍦3+6 )`h*410)c;n{bݖ@sO8v]FWWb} /d /((7QVCY!> -g0s)ĈOzx! ~\rQw:ܣ[io@pN_ `xK@d0s;]kag]3Fp^x jG@"r珗R"%wS:MH3gg~AlE%[?u$tb,rۖYw1ץȇ[/)]C}G. 4¿>Bo|{ԮBOtp3`)]>8Fr(`t{OAv>>6c¨o:7T`WqM=PX-KmRw/)Ø<:w`.l9>2c#7,Uޫ;E^"84BhL`n؍]>JUǹaL>zj+ҝ"!+wgeJIKǥgcO[ omņ4{3MTWU8t?ڎ՝ZCRX9@^ٳ\D1؏OT}݉67nÖƝhܺ wA"S~g5bjFj8FW@(xx9`cs1Kʈ%w~(},a|R`gQ8NN ۵}{hH=qtZM(ڡ%> +cpiIx0xZDc)ԝ%WL*0;]Q ] J^"0 MNpu2I ,\cn""P=KuB} Dٻ4P{ ( \D>c=X9DYۢVA`^]3kx<wN@`ny \]Ώ{2?>܉_e9AJdv>>8BGzw8)G[7AadAk81(;X1"IO%)%r9}L(صTt(l"iOAi <񖵺CP8r7QOZ9'25;@A"<;"9p-PDgR'9sN*=_ܢ,!#cbf(s;!fA@zKH_iqoQ+u RV()"r7YttKPج;Gś+Q,rJ(1uqqKCs)i %BZȳSn٣;9?="9*t N?bߊw6:Ǣ!2[E*a߱m=IʾYZQH7,ƞYT^ r%9?սRdVA"nm'Րiueψ }y7 _WsX>C+2,.e׼)eePt"I1yX2]"9riYu33)=tG "[;y@"YI)ԝ,;nOrRX6Pwy SȹX6?[5QJXJ~U;Ba.y[w\;F%w:{g֭8(-,rʚ`PgW$OuG cSH(\]ݠ;g|Ow0 S۹QXU/@ .%O@"@ ̜_SD[٪;y._;n"ӝENY'Tڠ;NA˺3wI \wIz=Ca(:ĐWw9iQ0}rqſyk6A"'mʽ3Bs^,r&uгΑU>ߣ#IM(A.۾PgKCI+òrd!-QKdfW:$۱I!ȉT˲r"9i'+" nҝɧ;Q3dw~׏PJXyC3dF߇{3*C)u;t~7Α!q ?Hre(ȳ,q$99Bޤ[;GFbI9<8,p'",bcKta+dWO8BQrWںܻ3ʟ>=O*!dj_rU9Tʭ!Ӏ>36?X\xiU}}|X"YQZ!Gɛ/oxQw;#,q99Tu9l뗺CPn`VշBLxmsPn`H҄|Gwt(=I)XH iE;99R5;;G* CI7vAEN%ӝ!~%O@EN|Fwd Uw-,rr,]`Pbܡ;99%!A˺3PaZIuERɝXh(@ݥ;@EN73t潲Qw M,rr+G9NHp@i"'W?tg8XBBw],rrp㫯pՒjCիNAEN uPԝl";NZ5b>Z;69FkB!cKੑʤa i->wTo,!5b@Yl-,k^PK@QWXw@L2)8B#ԝD\(@  'zZ,rr!n<7~%Ӿ*,rr%˲<5 :';ɵh:7ګ@7)e,/aUDb煮]E[O}YR"ܠΓ}&){MwLT)'TS'iKy;7r%9N@*(QR"a|:Cou#Jro 丅IV!bSRf3lD`SNQֲt"JrJdrME9Q xȿ}f1ݙ"#$t{EN9'2 xA)INDZ?;?F%tEXtdate:create2018-03-29T19:12:11-07:00U%tEXtdate:modify2018-03-29T19:12:11-07:00.tEXtSoftwarewww.inkscape.org<IENDB`icecream-2.1.3/logo.svg000066400000000000000000000310011426621446100147470ustar00rootroot00000000000000 image/svg+xml icecream-2.1.3/setup.cfg000066400000000000000000000001031426621446100151060ustar00rootroot00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE.txt icecream-2.1.3/setup.py000066400000000000000000000062341426621446100150120ustar00rootroot00000000000000#!/usr/bin/env python # -*- 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): pass def finalize_options(self): pass def run(self): os.system('python setup.py sdist bdist_wheel') sdist = 'dist/icecream-%s.tar.gz' % meta['__version__'] wheel = 'dist/icecream-%s-py2.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): 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(), include_package_data=True, classifiers=[ 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'Development Status :: 4 - Beta', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation :: CPython', ], tests_require=[], install_requires=[ 'colorama>=0.3.9', 'pygments>=2.2.0', 'executing>=0.3.1', 'asttokens>=2.0.1', ], cmdclass={ 'test': RunTests, 'publish': Publish, }, ) icecream-2.1.3/tests/000077500000000000000000000000001426621446100144355ustar00rootroot00000000000000icecream-2.1.3/tests/install_test_import.py000066400000000000000000000002661426621446100211120ustar00rootroot00000000000000# -*- 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) icecream-2.1.3/tests/test_icecream.py000066400000000000000000000476451426621446100176360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # IceCream - Never use print() to debug again # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: MIT # import functools import sys import unittest try: # Python 2.x. from StringIO import StringIO except ImportError: # Python 3.x. from io import StringIO from contextlib import contextmanager from os.path import basename, splitext, realpath import icecream from icecream import ic, argumentToString, stderrPrint, NoSourceAvailableError TEST_PAIR_DELIMITER = '| ' MY_FILENAME = basename(__file__) MY_FILEPATH = realpath(__file__) a = 1 b = 2 c = 3 def noop(*args, **kwargs): return def hasAnsiEscapeCodes(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 disableColoring(): originalOutputFunction = ic.outputFunction ic.configureOutput(outputFunction=stderrPrint) yield ic.configureOutput(outputFunction=originalOutputFunction) @contextmanager def configureIcecreamOutput(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 captureStandardStreams(): 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 stripPrefix(line): if line.startswith(ic.prefix): line = line.strip()[len(ic.prefix):] return line def lineIsContextAndTime(line): line = stripPrefix(line) # ic| f.py:33 in foo() at 08:08:51.389 context, time = line.split(' at ') return ( lineIsContext(context) and len(time.split(':')) == 3 and len(time.split('.')) == 2) def lineIsContext(line): line = stripPrefix(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 == '' or function.endswith('()'))) def lineIsAbsPathContext(line): line = stripPrefix(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 == '' or function.endswith('()'))) def lineAfterContext(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 parseOutputIntoPairs(out, err, assertNumLines, 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 assertNumLines: assert len(lines) == assertNumLines linePairs = [] for line in lines: line = lineAfterContext(line, prefix) if not line: linePairs.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 = linePairs[-1][-1] looksLikeAString = value[0] in ["'", '"'] prefix = (arg + ': ') + (' ' if looksLikeAString else '') dedented = line[len(ic.prefix) + len(prefix):] linePairs[-1][-1] = (arg, value + '\n' + dedented) else: items = [ (p[0].strip(), None) 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] linePairs.append(items) return linePairs class TestIceCream(unittest.TestCase): def setUp(self): ic._pairDelimiter = TEST_PAIR_DELIMITER def testMetadata(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 testWithoutArgs(self): with disableColoring(), captureStandardStreams() as (out, err): ic() assert lineIsContextAndTime(err.getvalue()) def testAsArgument(self): with disableColoring(), captureStandardStreams() as (out, err): noop(ic(a), ic(b)) pairs = parseOutputIntoPairs(out, err, 2) assert pairs[0][0] == ('a', '1') and pairs[1][0] == ('b', '2') with disableColoring(), captureStandardStreams() as (out, err): dic = {1: ic(a)} # noqa lst = [ic(b), ic()] # noqa pairs = parseOutputIntoPairs(out, err, 3) assert pairs[0][0] == ('a', '1') assert pairs[1][0] == ('b', '2') assert lineIsContextAndTime(err.getvalue().splitlines()[-1]) def testSingleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): ic(a) assert parseOutputIntoPairs(out, err, 1)[0][0] == ('a', '1') def testMultipleArguments(self): with disableColoring(), captureStandardStreams() as (out, err): ic(a, b) pairs = parseOutputIntoPairs(out, err, 1)[0] assert pairs == [('a', '1'), ('b', '2')] def testNestedMultiline(self): with disableColoring(), captureStandardStreams() as (out, err): ic( ) assert lineIsContextAndTime(err.getvalue()) with disableColoring(), captureStandardStreams() as (out, err): ic(a, 'foo') pairs = parseOutputIntoPairs(out, err, 1)[0] assert pairs == [('a', '1'), ("'foo'", None)] with disableColoring(), captureStandardStreams() as (out, err): noop(noop(noop({1: ic( noop())}))) assert parseOutputIntoPairs(out, err, 1)[0][0] == ('noop()', 'None') def testExpressionArguments(self): class klass(): attr = 'yep' d = {'d': {1: 'one'}, 'k': klass} with disableColoring(), captureStandardStreams() as (out, err): ic(d['d'][1]) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert pair == ("d['d'][1]", "'one'") with disableColoring(), captureStandardStreams() as (out, err): ic(d['k'].attr) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert pair == ("d['k'].attr", "'yep'") def testMultipleCallsOnSameLine(self): with disableColoring(), captureStandardStreams() as (out, err): ic(a); ic(b, c) # noqa pairs = parseOutputIntoPairs(out, err, 2) assert pairs[0][0] == ('a', '1') assert pairs[1] == [('b', '2'), ('c', '3')] def testCallSurroundedByExpressions(self): with disableColoring(), captureStandardStreams() as (out, err): noop(); ic(a); noop() # noqa assert parseOutputIntoPairs(out, err, 1)[0][0] == ('a', '1') def testComments(self): with disableColoring(), captureStandardStreams() as (out, err): """Comment."""; ic(); # Comment. # noqa assert lineIsContextAndTime(err.getvalue()) def testMethodArguments(self): class Foo: def foo(self): return 'foo' f = Foo() with disableColoring(), captureStandardStreams() as (out, err): ic(f.foo()) assert parseOutputIntoPairs(out, err, 1)[0][0] == ('f.foo()', "'foo'") def testComplicated(self): with disableColoring(), captureStandardStreams() as (out, err): noop(); ic(); noop(); ic(a, # noqa b, noop.__class__.__name__, # noqa noop ()); noop() # noqa pairs = parseOutputIntoPairs(out, err, 2) assert lineIsContextAndTime(err.getvalue().splitlines()[0]) assert pairs[1] == [ ('a', '1'), ('b', '2'), ('noop.__class__.__name__', "'function'"), ('noop ()', 'None')] def testReturnValue(self): with disableColoring(), captureStandardStreams() as (out, err): assert ic() is None assert ic(1) == 1 assert ic(1, 2, 3) == (1, 2, 3) def testDifferentName(self): from icecream import ic as foo with disableColoring(), captureStandardStreams() as (out, err): foo() assert lineIsContextAndTime(err.getvalue()) newname = foo with disableColoring(), captureStandardStreams() as (out, err): newname(a) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert pair == ('a', '1') def testPrefixConfiguration(self): prefix = 'lolsup ' with configureIcecreamOutput(prefix, stderrPrint): with disableColoring(), captureStandardStreams() as (out, err): ic(a) pair = parseOutputIntoPairs(out, err, 1, prefix=prefix)[0][0] assert pair == ('a', '1') def prefixFunction(): return 'lolsup ' with configureIcecreamOutput(prefix=prefixFunction): with disableColoring(), captureStandardStreams() as (out, err): ic(b) pair = parseOutputIntoPairs(out, err, 1, prefix=prefixFunction())[0][0] assert pair == ('b', '2') def testOutputFunction(self): lst = [] def appendTo(s): lst.append(s) with configureIcecreamOutput(ic.prefix, appendTo): with captureStandardStreams() as (out, err): ic(a) assert not out.getvalue() and not err.getvalue() with configureIcecreamOutput(outputFunction=appendTo): with captureStandardStreams() as (out, err): ic(b) assert not out.getvalue() and not err.getvalue() pairs = parseOutputIntoPairs(out, '\n'.join(lst), 2) assert pairs == [[('a', '1')], [('b', '2')]] def testEnableDisable(self): with disableColoring(), captureStandardStreams() 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 = parseOutputIntoPairs(out, err, 2) assert pairs == [[('a', '1')], [('c', '3')]] def testArgToStringFunction(self): def hello(obj): return 'zwei' with configureIcecreamOutput(argToStringFunction=hello): with disableColoring(), captureStandardStreams() as (out, err): eins = 'ein' ic(eins) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert pair == ('eins', 'zwei') def testSingledispatchArgumentToString(self): def argumentToString_tuple(obj): return "Dispatching tuple!" # Unsupport Python2 if "singledispatch" not in dir(functools): for attr in ("register", "unregister"): with self.assertRaises(NotImplementedError): getattr(argumentToString, attr)( tuple, argumentToString_tuple ) return # Prepare input and output x = (1, 2) default_output = ic.format(x) # Register argumentToString.register(tuple, argumentToString_tuple) assert tuple in argumentToString.registry assert str.endswith(ic.format(x), argumentToString_tuple(x)) # Unregister argumentToString.unregister(tuple) assert tuple not in argumentToString.registry assert ic.format(x) == default_output def testSingleArgumentLongLineNotWrapped(self): # A single long line with one argument is not line wrapped. longStr = '*' * (ic.lineWrapWidth + 1) with disableColoring(), captureStandardStreams() as (out, err): ic(longStr) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert len(err.getvalue()) > ic.lineWrapWidth assert pair == ('longStr', ic.argToStringFunction(longStr)) def testMultipleArgumentsLongLineWrapped(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 disableColoring(), captureStandardStreams() as (out, err): ic(v1, v2, v3, v4) pairs = parseOutputIntoPairs(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 testMultilineValueWrapped(self): # Multiline values are line wrapped. multilineStr = 'line1\nline2' with disableColoring(), captureStandardStreams() as (out, err): ic(multilineStr) pair = parseOutputIntoPairs(out, err, 2)[0][0] assert pair == ('multilineStr', ic.argToStringFunction(multilineStr)) def testIncludeContextSingleLine(self): i = 3 with configureIcecreamOutput(includeContext=True): with disableColoring(), captureStandardStreams() as (out, err): ic(i) pair = parseOutputIntoPairs(out, err, 1)[0][0] assert pair == ('i', '3') def testContextAbsPathSingleLine(self): i = 3 with configureIcecreamOutput(includeContext=True, contextAbsPath=True): with disableColoring(), captureStandardStreams() as (out, err): ic(i) # Output with absolute path can easily exceed line width, so no assert line num here. pairs = parseOutputIntoPairs(out, err, 0) assert [('i', '3')] in pairs def testValues(self): with disableColoring(), captureStandardStreams() as (out, err): # Test both 'asdf' and "asdf"; see # https://github.com/gruns/icecream/issues/53. ic(3, 'asdf', "asdf") pairs = parseOutputIntoPairs(out, err, 1) assert pairs == [[('3', None), ("'asdf'", None), ("'asdf'", None)]] def testIncludeContextMultiLine(self): multilineStr = 'line1\nline2' with configureIcecreamOutput(includeContext=True): with disableColoring(), captureStandardStreams() as (out, err): ic(multilineStr) firstLine = err.getvalue().splitlines()[0] assert lineIsContext(firstLine) pair = parseOutputIntoPairs(out, err, 3)[1][0] assert pair == ('multilineStr', ic.argToStringFunction(multilineStr)) def testContextAbsPathMultiLine(self): multilineStr = 'line1\nline2' with configureIcecreamOutput(includeContext=True, contextAbsPath=True): with disableColoring(), captureStandardStreams() as (out, err): ic(multilineStr) firstLine = err.getvalue().splitlines()[0] assert lineIsAbsPathContext(firstLine) pair = parseOutputIntoPairs(out, err, 3)[1][0] assert pair == ('multilineStr', ic.argToStringFunction(multilineStr)) def testFormat(self): with disableColoring(), captureStandardStreams() 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 testMultilineInvocationWithComments(self): with disableColoring(), captureStandardStreams() as (out, err): ic( # Comment. a, # Comment. # Comment. b, # Comment. ) # Comment. pairs = parseOutputIntoPairs(out, err, 1)[0] assert pairs == [('a', '1'), ('b', '2')] def testNoSourceAvailable(self): with disableColoring(), captureStandardStreams() as (out, err): eval('ic()') assert NoSourceAvailableError.infoMessage in err.getvalue() def testSingleTupleArgument(self): with disableColoring(), captureStandardStreams() as (out, err): ic((a, b)) pair = parseOutputIntoPairs(out, err, 1)[0][0] self.assertEqual(pair, ('(a, b)', '(1, 2)')) def testMultilineContainerArgs(self): with disableColoring(), captureStandardStreams() 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 disableColoring(), captureStandardStreams() as (out, err): with configureIcecreamOutput(includeContext=True): ic((a, b), [list(range(15)), list(range(15))]) lines = err.getvalue().strip().splitlines() self.assertRegexpMatches( lines[0], r'ic\| test_icecream.py:\d+ in testMultilineContainerArgs\(\)', ) 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 testMultipleTupleArguments(self): with disableColoring(), captureStandardStreams() as (out, err): ic((a, b), (b, a), a, b) pair = parseOutputIntoPairs(out, err, 1)[0] self.assertEqual(pair, [ ('(a, b)', '(1, 2)'), ('(b, a)', '(2, 1)'), ('a', '1'), ('b', '2')]) def testColoring(self): with captureStandardStreams() as (out, err): ic({1: 'str'}) # Output should be colored with ANSI control codes. assert hasAnsiEscapeCodes(err.getvalue()) def testConfigureOutputWithNoParameters(self): with self.assertRaises(TypeError): ic.configureOutput() icecream-2.1.3/tests/test_install.py000066400000000000000000000016001426621446100175110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # IceCream - Never use print() to debug again # # Ansgar Grunseid # grunseid.com # grunseid@gmail.com # # License: MIT # import unittest import icecream from test_icecream import ( disableColoring, captureStandardStreams, parseOutputIntoPairs) from install_test_import import runMe class TestIceCreamInstall(unittest.TestCase): def testInstall(self): icecream.install() with disableColoring(), captureStandardStreams() as (out, err): runMe() assert parseOutputIntoPairs(out, err, 1)[0][0] == ('x', '3') icecream.uninstall() # Clean up builtins. def testUninstall(self): try: icecream.uninstall() except AttributeError: # Already uninstalled. pass # NameError: global name 'ic' is not defined. with self.assertRaises(NameError): runMe() icecream-2.1.3/tox.ini000066400000000000000000000001751426621446100146110ustar00rootroot00000000000000[tox] envlist = py27, py35, py36, py37, py38, py39, pypy, pypy3 [testenv] deps = nose commands = nosetests --exe []