pax_global_header 0000666 0000000 0000000 00000000064 14266214461 0014520 g ustar 00root root 0000000 0000000 52 comment=cbd6da2fa02d480cd32df1e9e526c360318843f0
icecream-2.1.3/ 0000775 0000000 0000000 00000000000 14266214461 0013273 5 ustar 00root root 0000000 0000000 icecream-2.1.3/.gitignore 0000664 0000000 0000000 00000000101 14266214461 0015253 0 ustar 00root root 0000000 0000000 *~
.#*
\#*
.tox
dist/
.eggs/
build/
*.pyc
*.pyo
*.egg
*.egg-info
icecream-2.1.3/.travis.yml 0000664 0000000 0000000 00000000717 14266214461 0015411 0 ustar 00root root 0000000 0000000 language: 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.txt 0000664 0000000 0000000 00000002036 14266214461 0015117 0 ustar 00root root 0000000 0000000 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. icecream-2.1.3/MANIFEST.in 0000664 0000000 0000000 00000000036 14266214461 0015030 0 ustar 00root root 0000000 0000000 include LICENSE.txt README.md
icecream-2.1.3/README.md 0000664 0000000 0000000 00000022712 14266214461 0014556 0 ustar 00root root 0000000 0000000
### 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.txt 0000664 0000000 0000000 00000007067 14266214461 0015775 0 ustar 00root root 0000000 0000000 ================================================================================
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/ 0000775 0000000 0000000 00000000000 14266214461 0020045 5 ustar 00root root 0000000 0000000 icecream-2.1.3/failures-to-investigate/freshsales.py 0000664 0000000 0000000 00000031317 14266214461 0022563 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000032707 14266214461 0022651 0 ustar 00root root 0000000 0000000 #!/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.py 0000664 0000000 0000000 00000033302 14266214461 0022642 0 ustar 00root root 0000000 0000000 #!/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/ 0000775 0000000 0000000 00000000000 14266214461 0015043 5 ustar 00root root 0000000 0000000 icecream-2.1.3/icecream/__init__.py 0000664 0000000 0000000 00000000660 14266214461 0017156 0 ustar 00root root 0000000 0000000 # -*- 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__.py 0000664 0000000 0000000 00000000745 14266214461 0017704 0 ustar 00root root 0000000 0000000 # -*- 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.py 0000664 0000000 0000000 00000000602 14266214461 0017244 0 ustar 00root root 0000000 0000000 # -*- 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.py 0000664 0000000 0000000 00000007047 14266214461 0017241 0 ustar 00root root 0000000 0000000 # -*- 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.py 0000664 0000000 0000000 00000026003 14266214461 0017166 0 ustar 00root root 0000000 0000000 #!/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.png 0000664 0000000 0000000 00000033046 14266214461 0017637 0 ustar 00root root 0000000 0000000 PNG
IHDR r r $Q gAMA a cHRM z&