phelipetls / reportforce Goto Github PK
View Code? Open in Web Editor NEWA Salesforce Analytics API client for Python
Home Page: https://reportforce.readthedocs.io/
A Salesforce Analytics API client for Python
Home Page: https://reportforce.readthedocs.io/
I have a report that returns 8007 records.
As the package stands, it will add one filter for the id_column
. There are two issues with this.
First, it seems that Salesforce understands filters better when the values are quoted.
Second, there is a limit in the length of each filter value. Salesforce throws a BAD_REQUEST error if you have a filter that's too long.
One more constraint: "A report can't have more than 20 filters".
I'm not entirely sure how to accommodate the two constraints and still return all rows of an arbitrarily large report, but I do have a patch that at least gets you to 8007.
Haven't tested it, but quotation marks will need to be escaped in that map
lambda, and many value types should not be quoted. I was only testing with Id/Lookup fields
diff --git a/reportforce/helpers/generators.py b/reportforce/helpers/generators.py
index ff5bb22..566102f 100644
+++ a/reportforce/helpers/generators.py
--- b/reportforce/helpers/generators.py
@@ -31,13 +31,11 @@ def report_generator(get_report):
df = pd.DataFrame(report_cells, index=indices, columns=columns)
yield df
- if id_column:
- already_seen = ",".join(df[id_column].values)
- set_filters([(id_column, "!=", already_seen)], metadata)
-
- increment_logical_filter(metadata)
+ if id_column and len(df[id_column]):
+ inc = 2000
+ for ix in range(0, len(df[id_column]), inc):
+ already_seen = ",".join(map(lambda val: '"%s"' % val,
+ df[id_column][(ix*inc):inc+(ix*inc)]))
+ set_filters([(id_column, "!=", already_seen)], metadata)
+ increment_logical_filter(metadata)
while not report["allData"]:
# getting what is needed to build the dataframe
@@ -49,10 +47,8 @@ def report_generator(get_report):
yield df
# filtering out already seen values for the next report
- already_seen += ",".join(df[id_column].values)
- update_filter(-1, "value", already_seen, metadata)
+ already_seen = ",".join(map(lambda val: '"%s"' % val,
+ df[id_column][(ix*inc):inc+(ix*inc)]))
+ set_filters([(id_column, "!=", already_seen)], metadata)
+ increment_logical_filter(metadata)
@functools.wraps(get_report)
def concat(*args, **kwargs):
HI! Thanks for making such a useful package!
I found a few issues which I'll post as Issues here. Let me know if you're not up to maintaining this.
Username, Password and Security Token are not escaped in the SOAP auth, so if a password matches /[<>&]/g
, the auth will fail.
There's an easy fix, but I'm relatively novice at python and don't know know how to test it well.
The fix is this:
diff --git a/reportforce/helpers/xml.py b/reportforce/helpers/xml.py
index b07e140..c305057 100644
--- a/reportforce/helpers/xml.py
+++ b/reportforce/helpers/xml.py
@@ -1,11 +1,15 @@
import urllib
import xml.etree.ElementTree as ET
+import xml.sax.saxutils
namespace = {
"soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
"urn": "urn:partner.soap.sforce.com",
}
+def escape(s):
+ """escape a value for XML"""
+ return saxutils.escape(s)
def read_successful_response(response):
"""Parse XML returned by SOAP API response in case of a successful login.
diff --git a/reportforce/login.py b/reportforce/login.py
index 7c47e4f..e4db73c 100644
--- a/reportforce/login.py
+++ b/reportforce/login.py
@@ -1,7 +1,7 @@
import getpass
import requests
-from .helpers.xml import read_failed_response, read_successful_response
+from .helpers.xml import read_failed_response, read_successful_response, escape
DEFAULT_VERSION = "47.0"
@@ -111,9 +111,9 @@ def soap_login(username, password, security_token, version="47.0", domain="login
</n1:login>
</env:Body>
</env:Envelope>""".format(
- username=username,
- password=password if password else getpass.getpass(),
- token=security_token if security_token else getpass.getpass("Security Token: "),
+ username=escape(username),
+ password=escape(password) if password else escape(getpass.getpass()),
+ token=escape(security_token) if security_token else escape(getpass.getpass("Security Token: ")),
)
response = requests.post(soap_url, headers=soap_headers, data=soap_body)
Hi, thanks for sharing your work. I am just not sure how do I find the case number for column ID? Could you provide guide?
pandas
and requests
are spec'ed out dependencies in requirements.txt
, but unless they're declared in setup.py
, pip won't install them. They should probably also come with a version.
Hi there, I like the idea for the package and want to try it out. My team uses the anaconda environment to keep us uniform. Pip install may break things for us. Is there a conda install option?
Although soap_login
method allows setting a domain, __init__
doesn't allow you to pass in a non-production domain.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.