blueink-client-python 1.0.1__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {blueink_client_python-1.0.1/src/blueink_client_python.egg-info → blueink_client_python-1.2.0}/PKG-INFO +4 -3
  2. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/setup.cfg +5 -9
  3. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/bundle_helper.py +50 -0
  4. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/endpoints.py +3 -0
  5. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/model/bundles.py +52 -0
  6. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/bundle.py +29 -0
  7. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/template.py +41 -0
  8. blueink_client_python-1.2.0/src/blueink/tests/test_bundle_helper.py +634 -0
  9. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/tests/test_client.py +32 -0
  10. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0/src/blueink_client_python.egg-info}/PKG-INFO +4 -3
  11. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink_client_python.egg-info/requires.txt +2 -2
  12. blueink_client_python-1.0.1/src/blueink/tests/test_bundle_helper.py +0 -318
  13. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/LICENSE +0 -0
  14. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/README.md +0 -0
  15. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/pyproject.toml +0 -0
  16. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/__init__.py +0 -0
  17. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/client.py +0 -0
  18. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/constants.py +0 -0
  19. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/model/__init__.py +0 -0
  20. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/model/persons.py +0 -0
  21. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/model/webhook.py +0 -0
  22. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/paginator.py +0 -0
  23. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/person_helper.py +0 -0
  24. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/request_helper.py +0 -0
  25. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/__init__.py +0 -0
  26. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/envelope_template.py +0 -0
  27. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/packet.py +0 -0
  28. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/person.py +0 -0
  29. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/subclient.py +0 -0
  30. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/subclients/webhook.py +0 -0
  31. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/tests/__init__.py +0 -0
  32. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/tests/test_person_helper.py +0 -0
  33. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/utils/__init__.py +0 -0
  34. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink/utils/testcase.py +0 -0
  35. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink_client_python.egg-info/SOURCES.txt +0 -0
  36. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink_client_python.egg-info/dependency_links.txt +0 -0
  37. {blueink_client_python-1.0.1 → blueink_client_python-1.2.0}/src/blueink_client_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: blueink-client-python
3
- Version: 1.0.1
3
+ Version: 1.2.0
4
4
  Summary: Python Client for Blueink eSignature API
5
5
  Home-page: https://github.com/blueinkhq/blueink-client-python
6
6
  Author: Blueink
@@ -23,7 +23,8 @@ Requires-Dist: requests>=2.31; extra == "requests"
23
23
  Provides-Extra: pydantic
24
24
  Requires-Dist: pydantic>=1.9; extra == "pydantic"
25
25
  Provides-Extra: email-validator
26
- Requires-Dist: email-validator>=1.2; extra == "email-validator"
26
+ Requires-Dist: 1.2; extra == "email-validator"
27
+ Dynamic: license-file
27
28
 
28
29
  # blueink-client-python
29
30
  ![Tests](https://github.com/blueinkhq/blueink-client-python/actions/workflows/helper-tests.yml/badge.svg)
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = blueink-client-python
3
- version = 1.0.1
3
+ version = 1.2.0
4
4
  author = Blueink
5
5
  author_email = pypi@blueink.com
6
6
  description = Python Client for Blueink eSignature API
@@ -26,14 +26,10 @@ install_requires =
26
26
  email-validator
27
27
 
28
28
  [options.extras_require]
29
- munch =
30
- munch>=2.5
31
- requests =
32
- requests>=2.31
33
- pydantic =
34
- pydantic>=1.9
35
- email-validator =
36
- email-validator>=1.2
29
+ munch = munch>=2.5;
30
+ requests = requests>=2.31;
31
+ pydantic = pydantic>=1.9
32
+ email-validator> = 1.2
37
33
 
38
34
  [options.packages.find]
39
35
  where = src
@@ -10,6 +10,7 @@ from blueink.model.bundles import (
10
10
  EnvelopeTemplate,
11
11
  EnvelopeTemplateFieldValue,
12
12
  Field,
13
+ ImportedDocument,
13
14
  Packet,
14
15
  TemplateRef,
15
16
  TemplateRefAssignment,
@@ -28,6 +29,9 @@ class BundleHelper:
28
29
  is_test: bool = False,
29
30
  custom_key: str = None,
30
31
  team: str = None,
32
+ signing_brand: str = None,
33
+ expires: str = None,
34
+ tag_values: dict = None,
31
35
  ):
32
36
  """Helper class to aid building a Bundle.
33
37
 
@@ -41,6 +45,9 @@ class BundleHelper:
41
45
  is_test:
42
46
  custom_key:
43
47
  team:
48
+ signing_brand:
49
+ expires:
50
+ tag_values: Optional dict mapping data flow tags to pre-filled values
44
51
  """
45
52
  self._label = label
46
53
  self._in_order = in_order
@@ -52,6 +59,9 @@ class BundleHelper:
52
59
  self._packets = {}
53
60
  self._custom_key = custom_key
54
61
  self._team = team
62
+ self._signing_brand = signing_brand
63
+ self._expires = expires
64
+ self._tag_values = tag_values
55
65
  self._envelope_template = None
56
66
 
57
67
  # for file uploads, index should match those in the document "file_index" field
@@ -111,6 +121,20 @@ class BundleHelper:
111
121
 
112
122
  return self.add_document_by_b64(filename, b64str, **additional_data)
113
123
 
124
+ def add_document_by_html(self, html_content: str, **additional_data) -> str:
125
+ """Add a document using an HTML string for HTML-to-PDF conversion.
126
+
127
+ Args:
128
+ html_content: HTML content string
129
+ additional_data: Optional additional kwargs (e.g., filename, html_fields_mode)
130
+
131
+ Returns:
132
+ Document Key
133
+ """
134
+ document = Document.create(file_html=html_content, **additional_data)
135
+ self._documents[document.key] = document
136
+ return document.key
137
+
114
138
  def add_document_by_b64(self, filename: str, b64str: str, **additional_data):
115
139
  """Add a file using a b64 string; utf-8 encoded
116
140
 
@@ -209,6 +233,10 @@ class BundleHelper:
209
233
  v_pattern: str = None,
210
234
  v_min: int = None,
211
235
  v_max: int = None,
236
+ v_regex: str = None,
237
+ v_regex_msg: str = None,
238
+ v_attachment_types: List[str] = None,
239
+ data_flow_tag: str = None,
212
240
  key=None,
213
241
  **additional_data,
214
242
  ):
@@ -226,6 +254,10 @@ class BundleHelper:
226
254
  v_pattern: Optional
227
255
  v_min: Optional
228
256
  v_max: Optional
257
+ v_regex: Optional regular expression for field validation
258
+ v_regex_msg: Optional error message shown when v_regex validation fails
259
+ v_attachment_types: Optional list of allowed attachment file extensions (only for kind='att')
260
+ data_flow_tag: Optional data flow tag to associate with the field
229
261
  editors: Optional
230
262
  key: Optional
231
263
  additional_data: Optional and will append any additional kwargs to the json of the field
@@ -247,6 +279,10 @@ class BundleHelper:
247
279
  v_pattern=v_pattern,
248
280
  v_min=v_min,
249
281
  v_max=v_max,
282
+ v_regex=v_regex,
283
+ v_regex_msg=v_regex_msg,
284
+ v_attachment_types=v_attachment_types,
285
+ data_flow_tag=data_flow_tag,
250
286
  key=key,
251
287
  **additional_data,
252
288
  )
@@ -267,6 +303,7 @@ class BundleHelper:
267
303
  offset_y: int = 0,
268
304
  editors: List[str] = None,
269
305
  page: int = None,
306
+ v_attachment_types: List[str] = None,
270
307
  **additional_data,
271
308
  ):
272
309
  """Add an auto-placement field to a document.
@@ -284,6 +321,7 @@ class BundleHelper:
284
321
  offset_y: Vertical offset from the search text (default: 0)
285
322
  editors: List of signer keys who can edit this field
286
323
  page: Optional page number to limit search to
324
+ v_attachment_types: Optional list of allowed attachment file extensions (only for kind='att')
287
325
  additional_data: Optional additional kwargs to append to the auto-placement
288
326
 
289
327
  Returns:
@@ -313,6 +351,7 @@ class BundleHelper:
313
351
  offset_x=offset_x,
314
352
  offset_y=offset_y,
315
353
  page=page,
354
+ v_attachment_types=v_attachment_types,
316
355
  **additional_data,
317
356
  )
318
357
 
@@ -334,6 +373,8 @@ class BundleHelper:
334
373
  auth_id: bool = False,
335
374
  order: int = None,
336
375
  key=None,
376
+ requires_witness: bool = None,
377
+ witness_nominated_by: str = None,
337
378
  **additional_data,
338
379
  ):
339
380
  """Create and add a signer. With at least an email xor phone number.
@@ -349,6 +390,8 @@ class BundleHelper:
349
390
  auth_id: Optional
350
391
  deliver_via: Optional
351
392
  order: Optional
393
+ requires_witness: Optional
394
+ witness_nominated_by: Optional
352
395
  additional_data: Optional and will append any additional kwargs to the json of the signer
353
396
 
354
397
  Returns:
@@ -368,6 +411,8 @@ class BundleHelper:
368
411
  deliver_via=deliver_via,
369
412
  order=order,
370
413
  key=key,
414
+ requires_witness=requires_witness,
415
+ witness_nominated_by=witness_nominated_by,
371
416
  **additional_data,
372
417
  )
373
418
  self._packets[packet.key] = packet
@@ -496,6 +541,9 @@ class BundleHelper:
496
541
  cc_emails=self._cc_emails,
497
542
  custom_key=self._custom_key,
498
543
  team=self._team,
544
+ signing_brand=self._signing_brand,
545
+ expires=self._expires,
546
+ tag_values=self._tag_values,
499
547
  **additional_data,
500
548
  )
501
549
  return bundle_out
@@ -561,6 +609,8 @@ class BundleHelper:
561
609
  result["custom_key"] = self._custom_key
562
610
  if self._team:
563
611
  result["team"] = self._team
612
+ if self._signing_brand:
613
+ result["signing_brand"] = self._signing_brand
564
614
 
565
615
  # Add any additional data
566
616
  result.update(additional_data)
@@ -9,6 +9,7 @@ from string import Template
9
9
  class BUNDLES:
10
10
  CREATE = "/bundles/"
11
11
  CREATE_FROM_ENVELOPE_TEMPLATE = "/bundles/create_from_envelope_template/"
12
+ CREATE_PREPARATION_SESSION = "/bundles/preparation_session/"
12
13
  LIST = "/bundles/"
13
14
  RETRIEVE = "/bundles/${bundle_id}/"
14
15
  CANCEL = "/bundles/${bundle_id}/cancel/"
@@ -35,6 +36,8 @@ class PACKETS:
35
36
  class TEMPLATES:
36
37
  LIST = "/templates/"
37
38
  RETRIEVE = "/templates/${template_id}/"
39
+ UPDATE = "/templates/${template_id}/"
40
+ CREATE_PREPARATION_SESSION = "/templates/preparation_session/"
38
41
 
39
42
 
40
43
  class ENVELOPE_TEMPLATES:
@@ -28,6 +28,7 @@ class AutoPlacement(BaseModel):
28
28
  offset_y: Optional[int] = 0
29
29
  editors: Optional[List[str]]
30
30
  page: Optional[int]
31
+ v_attachment_types: Optional[List[str]]
31
32
 
32
33
  class Config:
33
34
  extra = "allow"
@@ -93,7 +94,11 @@ class Field(BaseModel):
93
94
  v_pattern: Optional[int]
94
95
  v_min: Optional[int]
95
96
  v_max: Optional[int]
97
+ v_regex: Optional[str]
98
+ v_regex_msg: Optional[str]
96
99
  editors: Optional[List[str]]
100
+ v_attachment_types: Optional[List[str]]
101
+ data_flow_tag: Optional[str]
97
102
 
98
103
  class Config:
99
104
  extra = "allow"
@@ -129,6 +134,8 @@ class Packet(BaseModel):
129
134
  deliver_via: Optional[str]
130
135
  person_id: Optional[str]
131
136
  order: Optional[str]
137
+ requires_witness: Optional[bool]
138
+ witness_nominated_by: Optional[str]
132
139
 
133
140
  class Config:
134
141
  extra = "allow"
@@ -246,9 +253,11 @@ class Document(BaseModel):
246
253
  file_url: Optional[str]
247
254
  filename: Optional[str]
248
255
  file_b64: Optional[str]
256
+ file_html: Optional[str]
249
257
  file_index: Optional[int]
250
258
  fields: Optional[List[Field]]
251
259
  auto_placements: Optional[List[AutoPlacement]]
260
+ html_fields_mode: Optional[str]
252
261
 
253
262
  class Config:
254
263
  extra = "allow"
@@ -281,6 +290,46 @@ class Document(BaseModel):
281
290
  self.assignments.append(assignment)
282
291
 
283
292
 
293
+ class ImportedDocument(BaseModel):
294
+ """Model for documents in an imported bundle.
295
+
296
+ Each imported document must have exactly one of file_b64 or file_html.
297
+ Providing both or neither will raise a ValidationError.
298
+ """
299
+
300
+ key: Optional[str]
301
+ filename: Optional[str]
302
+ file_b64: Optional[str]
303
+ file_html: Optional[str]
304
+ file_url: Optional[str]
305
+ file_index: Optional[int]
306
+ fields: Optional[list]
307
+
308
+ class Config:
309
+ extra = "allow"
310
+
311
+ @validator("file_html", always=True)
312
+ def validate_file_source(cls, file_html, values):
313
+ file_b64 = values.get("file_b64")
314
+ has_b64 = bool(file_b64)
315
+ has_html = bool(file_html)
316
+
317
+ if has_b64 and has_html:
318
+ raise ValueError("Provide only one of 'file_b64' or 'file_html', not both")
319
+
320
+ if not has_b64 and not has_html:
321
+ raise ValueError("Each document must have either 'file_b64' or 'file_html'")
322
+
323
+ return file_html
324
+
325
+ @classmethod
326
+ def create(cls, key=None, **kwargs):
327
+ if not key:
328
+ key = generate_key("doc", 5)
329
+ obj = ImportedDocument(key=key, **kwargs)
330
+ return obj
331
+
332
+
284
333
  class Bundle(BaseModel):
285
334
  packets: List[Packet] = ...
286
335
  documents: List[Document] = ...
@@ -292,6 +341,9 @@ class Bundle(BaseModel):
292
341
  is_test: Optional[bool]
293
342
  custom_key: Optional[str]
294
343
  team: Optional[str]
344
+ signing_brand: Optional[str]
345
+ expires: Optional[str]
346
+ tag_values: Optional[dict]
295
347
 
296
348
  class Config:
297
349
  extra = "allow"
@@ -320,3 +320,32 @@ class BundleSubClient(SubClient):
320
320
  """
321
321
  url = self.build_url(endpoints.BUNDLES.LIST_DATA, bundle_id=bundle_id)
322
322
  return self._requests.get(url)
323
+
324
+ def create_preparation_session(self, data: dict) -> NormalizedResponse:
325
+ """Create an embedded document preparation session.
326
+
327
+ Creates a secure, time-limited URL that can be embedded in an iframe to allow
328
+ end users to prepare documents for signing through your application.
329
+
330
+ At least one document source must be enabled: either upload_pdf=True or
331
+ template_ids/folder_ids must be provided.
332
+
333
+ Args:
334
+ data: configuration for the preparation session. Supported keys:
335
+ - upload_pdf (bool): whether to allow PDF uploads (default True)
336
+ - draft_bundle (str, optional): slug of an existing draft bundle
337
+ - template_ids (list, optional): list of template UUIDs to restrict to
338
+ - folder_ids (list, optional): list of folder BUIDs to restrict to
339
+ - redirect_url (str, optional): URL to redirect to after preparation
340
+ - allow_search_signers (bool, optional): whether to allow signer search
341
+
342
+ Returns:
343
+ NormalizedResponse object with keys:
344
+ - url: the preparation session URL to embed in an iframe
345
+ - expires: ISO 8601 timestamp when the session URL expires
346
+ """
347
+ if not data:
348
+ raise ValueError("data is required")
349
+
350
+ url = self.build_url(endpoints.BUNDLES.CREATE_PREPARATION_SESSION)
351
+ return self._requests.post(url, json=data)
@@ -56,3 +56,44 @@ class TemplateSubClient(SubClient):
56
56
  """
57
57
  url = self.build_url(endpoints.TEMPLATES.RETRIEVE, template_id=template_id)
58
58
  return self._requests.get(url)
59
+
60
+ def update(self, template_id: str, data: dict) -> NormalizedResponse:
61
+ """Partially update a Template.
62
+
63
+ Typically used to write template metadata, e.g.
64
+ ``{"metadata": {"key": "value"}}``.
65
+
66
+ Args:
67
+ template_id:
68
+ data: dict of fields to update on the template
69
+
70
+ Returns:
71
+ NormalizedResponse object
72
+ """
73
+ if not data:
74
+ raise ValueError("data is required")
75
+
76
+ url = self.build_url(endpoints.TEMPLATES.UPDATE, template_id=template_id)
77
+ return self._requests.patch(url, json=data)
78
+
79
+ def create_preparation_session(self, data: dict) -> NormalizedResponse:
80
+ """Create an embedded template preparation session.
81
+
82
+ Creates a secure, time-limited URL that can be embedded in an iframe to allow
83
+ end users to prepare a template through your application.
84
+
85
+ Args:
86
+ data: configuration for the preparation session. Supported keys:
87
+ - template_id (str, optional): slug of an existing template to edit
88
+ - redirect_url (str, optional): URL to redirect to after preparation
89
+
90
+ Returns:
91
+ NormalizedResponse object with keys:
92
+ - url: the preparation session URL to embed in an iframe
93
+ - expires: ISO 8601 timestamp when the session URL expires
94
+ """
95
+ if not data:
96
+ raise ValueError("data is required")
97
+
98
+ url = self.build_url(endpoints.TEMPLATES.CREATE_PREPARATION_SESSION)
99
+ return self._requests.post(url, json=data)