gopichandra commited on
Commit
a2e1ba8
·
verified ·
1 Parent(s): 4c5a679

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -22
app.py CHANGED
@@ -15,8 +15,8 @@ from utils import extract_kyc_fields
15
  SF_USERNAME = "[email protected]"
16
  SF_PASSWORD = "Lic@2025"
17
  SF_SECURITY_TOKEN = "AmmfRcd6IiYaRtSGntBnzNMQU"
18
- SF_DOMAIN = "login" # "login" for prod, "test" for sandbox
19
- SF_API_VERSION = "60.0" # safe current Partner/REST version
20
  # -------------------------------------------------------------------------------
21
 
22
 
@@ -57,14 +57,12 @@ def _parse_birthdate(dob_text: str):
57
  return None
58
 
59
 
60
- def _soap_login(username: str, password: str, token: str, domain: str = "login", api_version: str = SF_API_VERSION):
61
  """
62
- Login via Salesforce Partner SOAP API (no connected app needed).
63
- Returns (instance_url, access_token) on success; raises on error.
64
  """
65
  endpoint = f"https://{domain}.salesforce.com/services/Soap/u/{api_version}"
66
- combined_password = f"{password}{token}"
67
-
68
  envelope = f"""<?xml version="1.0" encoding="utf-8" ?>
69
  <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
70
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -72,7 +70,7 @@ def _soap_login(username: str, password: str, token: str, domain: str = "login",
72
  <env:Body>
73
  <n1:login xmlns:n1="urn:partner.soap.sforce.com">
74
  <n1:username>{username}</n1:username>
75
- <n1:password>{combined_password}</n1:password>
76
  </n1:login>
77
  </env:Body>
78
  </env:Envelope>"""
@@ -84,7 +82,12 @@ def _soap_login(username: str, password: str, token: str, domain: str = "login",
84
 
85
  resp = requests.post(endpoint, data=envelope.encode("utf-8"), headers=headers, timeout=30)
86
  if resp.status_code != 200:
87
- raise RuntimeError(f"SOAP login HTTP {resp.status_code}: {resp.text[:400]}")
 
 
 
 
 
88
 
89
  # Parse XML to find sessionId and serverUrl
90
  try:
@@ -92,7 +95,6 @@ def _soap_login(username: str, password: str, token: str, domain: str = "login",
92
  except ET.ParseError as e:
93
  raise RuntimeError(f"SOAP login parse error: {e}")
94
 
95
- # Find tags by localname to avoid namespace headaches
96
  session_id = None
97
  server_url = None
98
  for elem in root.iter():
@@ -103,16 +105,68 @@ def _soap_login(username: str, password: str, token: str, domain: str = "login",
103
  server_url = elem.text
104
 
105
  if not session_id or not server_url:
106
- raise RuntimeError(f"SOAP login failed: sessionId/serverUrl not found. Raw: {resp.text[:600]}")
 
107
 
108
  parsed = urlparse(server_url)
109
  instance_url = f"{parsed.scheme}://{parsed.netloc}"
110
  return instance_url, session_id
111
 
112
 
113
- def _rest_create(instance_url: str, access_token: str, object_api: str, payload: dict, api_version: str = SF_API_VERSION):
114
  """
115
- Create a record via REST API. Returns a dict with success flag and details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  """
117
  url = f"{instance_url}/services/data/v{api_version}/sobjects/{object_api}"
118
  headers = {
@@ -130,7 +184,6 @@ def _rest_create(instance_url: str, access_token: str, object_api: str, payload:
130
  rec_id = body.get("id") if isinstance(body, dict) else None
131
  return {"success": True, "id": rec_id, "status_code": resp.status_code, "url": url, "response_json": body}
132
  else:
133
- # Salesforce error lists come back as JSON arrays usually
134
  return {
135
  "success": False,
136
  "status_code": resp.status_code,
@@ -143,7 +196,7 @@ def _rest_create(instance_url: str, access_token: str, object_api: str, payload:
143
  def sf_push_kyc_record(ocr_results: dict):
144
  """
145
  Combine Aadhaar + PAN into one KYC_Record__c and create via REST.
146
- Fields expected in SF (API names):
147
  Aadhaar_Number__c, Aadhaar_Name__c, Aadhaar_DOB__c (Date)
148
  PAN_Number__c, Pan_Name__c, Pan_DOB__c (Date)
149
  """
@@ -168,17 +221,21 @@ def sf_push_kyc_record(ocr_results: dict):
168
  }
169
  payload = {k: v for k, v in payload.items() if v is not None}
170
 
171
- # 1) SOAP login
172
- instance_url, access_token = _soap_login(SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, SF_DOMAIN, SF_API_VERSION)
 
 
173
  # 2) REST create
174
- return _rest_create(instance_url, access_token, "KYC_Record__c", payload, SF_API_VERSION)
 
 
175
 
176
 
177
  # ---------- gradio callback ----------
178
  def process_documents(aadhaar_file, pan_file, push_to_sf):
179
  """
180
  - Runs OCR on Aadhaar and PAN separately.
181
- - Optionally pushes a single KYC_Record__c to Salesforce via SOAP+REST (no simple-salesforce).
182
  """
183
  results = {"aadhaar": None, "pan": None}
184
 
@@ -210,7 +267,10 @@ def process_documents(aadhaar_file, pan_file, push_to_sf):
210
  created = sf_push_kyc_record(results)
211
  output["salesforce"] = {"pushed": created.get("success", False), **created}
212
  except Exception as e:
213
- output["salesforce"] = {"pushed": False, "error": {"type": e.__class__.__name__, "message": str(e)}}
 
 
 
214
 
215
  return output
216
 
@@ -250,10 +310,11 @@ with gr.Blocks(title="Smart KYC OCR → Salesforce (KYC_Record__c)") as demo:
250
  gr.Markdown("---")
251
  gr.Markdown(
252
  """
253
- You’ll see `status_code`, `url`, and Salesforce’s `response_json` on errors (e.g., wrong field API name or permissions).
 
254
  """
255
  )
256
 
257
- # Keep `demo` at module level for Spaces too
258
  if __name__ == "__main__":
259
  demo.launch()
 
15
  SF_USERNAME = "[email protected]"
16
  SF_PASSWORD = "Lic@2025"
17
  SF_SECURITY_TOKEN = "AmmfRcd6IiYaRtSGntBnzNMQU"
18
+ SF_DOMAIN = "login" # "login" for prod, "test" for sandbox (we will auto-try both)
19
+ SF_API_VERSION = "60.0" # REST & Partner SOAP API version
20
  # -------------------------------------------------------------------------------
21
 
22
 
 
57
  return None
58
 
59
 
60
+ def _soap_login_once(username: str, password: str, domain: str, api_version: str):
61
  """
62
+ One SOAP login attempt with given password (already concatenated or not).
63
+ Returns (instance_url, session_id) on success; raises RuntimeError on failure with fault details.
64
  """
65
  endpoint = f"https://{domain}.salesforce.com/services/Soap/u/{api_version}"
 
 
66
  envelope = f"""<?xml version="1.0" encoding="utf-8" ?>
67
  <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
68
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 
70
  <env:Body>
71
  <n1:login xmlns:n1="urn:partner.soap.sforce.com">
72
  <n1:username>{username}</n1:username>
73
+ <n1:password>{password}</n1:password>
74
  </n1:login>
75
  </env:Body>
76
  </env:Envelope>"""
 
82
 
83
  resp = requests.post(endpoint, data=envelope.encode("utf-8"), headers=headers, timeout=30)
84
  if resp.status_code != 200:
85
+ # Try to parse a SOAP fault if present
86
+ faultcode, faultstring = _extract_fault(resp.text)
87
+ msg = f"HTTP {resp.status_code}"
88
+ if faultcode or faultstring:
89
+ msg += f" | faultcode={faultcode} | faultstring={faultstring}"
90
+ raise RuntimeError(msg)
91
 
92
  # Parse XML to find sessionId and serverUrl
93
  try:
 
95
  except ET.ParseError as e:
96
  raise RuntimeError(f"SOAP login parse error: {e}")
97
 
 
98
  session_id = None
99
  server_url = None
100
  for elem in root.iter():
 
105
  server_url = elem.text
106
 
107
  if not session_id or not server_url:
108
+ faultcode, faultstring = _extract_fault(resp.text)
109
+ raise RuntimeError(f"SOAP login failed: sessionId/serverUrl not found | faultcode={faultcode} | faultstring={faultstring}")
110
 
111
  parsed = urlparse(server_url)
112
  instance_url = f"{parsed.scheme}://{parsed.netloc}"
113
  return instance_url, session_id
114
 
115
 
116
+ def _extract_fault(xml_text: str):
117
  """
118
+ Extract SOAP faultcode and faultstring for clearer errors.
119
+ """
120
+ try:
121
+ root = ET.fromstring(xml_text)
122
+ except Exception:
123
+ return None, None
124
+ faultcode = None
125
+ faultstring = None
126
+ for elem in root.iter():
127
+ tag = elem.tag.split('}', 1)[-1]
128
+ if tag == "faultcode":
129
+ faultcode = elem.text
130
+ elif tag == "faultstring":
131
+ faultstring = elem.text
132
+ return faultcode, faultstring
133
+
134
+
135
+ def soap_login_all_paths(username: str, password: str, token: str, preferred_domain: str = "login", api_version: str = SF_API_VERSION):
136
+ """
137
+ Try multiple safe login permutations:
138
+ 1) preferred_domain with password+token
139
+ 2) preferred_domain with password only
140
+ 3) alternate domain with password+token
141
+ 4) alternate domain with password only
142
+
143
+ Returns (instance_url, session_id, diagnostics) on success.
144
+ On failure, raises RuntimeError with aggregated diagnostics.
145
+ """
146
+ domains = [preferred_domain] + [d for d in ["login", "test"] if d != preferred_domain]
147
+ attempts = []
148
+ for domain in domains:
149
+ for mode in ["pw_token", "pw_only"]:
150
+ pw = f"{password}{token}" if mode == "pw_token" else password
151
+ try:
152
+ instance_url, session_id = _soap_login_once(username, pw, domain, api_version)
153
+ diag = {"domain": domain, "mode": mode, "result": "success"}
154
+ attempts.append(diag)
155
+ return instance_url, session_id, attempts
156
+ except Exception as e:
157
+ # Collect reason but keep trying
158
+ attempts.append({"domain": domain, "mode": mode, "result": "fail", "reason": str(e)})
159
+
160
+ # If we’re here, all attempts failed
161
+ raise RuntimeError(json.dumps({
162
+ "message": "All SOAP login attempts failed",
163
+ "attempts": attempts
164
+ }))
165
+
166
+
167
+ def rest_create(instance_url: str, access_token: str, object_api: str, payload: dict, api_version: str = SF_API_VERSION):
168
+ """
169
+ Create a record via REST API. Returns dict with success flag and full details.
170
  """
171
  url = f"{instance_url}/services/data/v{api_version}/sobjects/{object_api}"
172
  headers = {
 
184
  rec_id = body.get("id") if isinstance(body, dict) else None
185
  return {"success": True, "id": rec_id, "status_code": resp.status_code, "url": url, "response_json": body}
186
  else:
 
187
  return {
188
  "success": False,
189
  "status_code": resp.status_code,
 
196
  def sf_push_kyc_record(ocr_results: dict):
197
  """
198
  Combine Aadhaar + PAN into one KYC_Record__c and create via REST.
199
+ Fields expected (API names):
200
  Aadhaar_Number__c, Aadhaar_Name__c, Aadhaar_DOB__c (Date)
201
  PAN_Number__c, Pan_Name__c, Pan_DOB__c (Date)
202
  """
 
221
  }
222
  payload = {k: v for k, v in payload.items() if v is not None}
223
 
224
+ # 1) SOAP login (auto-tries domains & password modes)
225
+ instance_url, access_token, diagnostics = soap_login_all_paths(
226
+ SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, preferred_domain=SF_DOMAIN, api_version=SF_API_VERSION
227
+ )
228
  # 2) REST create
229
+ rest_res = rest_create(instance_url, access_token, "KYC_Record__c", payload, SF_API_VERSION)
230
+ rest_res["login_diagnostics"] = diagnostics
231
+ return rest_res
232
 
233
 
234
  # ---------- gradio callback ----------
235
  def process_documents(aadhaar_file, pan_file, push_to_sf):
236
  """
237
  - Runs OCR on Aadhaar and PAN separately.
238
+ - Optionally pushes a single KYC_Record__c to Salesforce via SOAP+REST.
239
  """
240
  results = {"aadhaar": None, "pan": None}
241
 
 
267
  created = sf_push_kyc_record(results)
268
  output["salesforce"] = {"pushed": created.get("success", False), **created}
269
  except Exception as e:
270
+ output["salesforce"] = {
271
+ "pushed": False,
272
+ "error": {"type": e.__class__.__name__, "message": str(e)}
273
+ }
274
 
275
  return output
276
 
 
310
  gr.Markdown("---")
311
  gr.Markdown(
312
  """
313
+ On failure you’ll see a `login_diagnostics` list (each attempt with domain+mode+reason)
314
+ and REST `status_code/url/response_json`. This eliminates the SDK TypeError and pinpoints issues fast.
315
  """
316
  )
317
 
318
+ # Keep `demo` available for local run / Spaces
319
  if __name__ == "__main__":
320
  demo.launch()