`__
for more info.
content (bytes):
- Inline document content, represented as a stream of bytes.
- Note: As with all ``bytes`` fields, protobuffers use a pure
- binary representation, whereas JSON representations use
- base64.
+ Optional. Inline document content, represented as a stream
+ of bytes. Note: As with all ``bytes`` fields, protobuffers
+ use a pure binary representation, whereas JSON
+ representations use base64.
mime_type (str):
An IANA published MIME type (also referred to
as media type). For more information, see
https://www.iana.org/assignments/media-
types/media-types.xhtml.
text (str):
- UTF-8 encoded text in reading order from the
- document.
- text_styles (Sequence[~.document.Document.Style]):
+ Optional. UTF-8 encoded text in reading order
+ from the document.
+ text_styles (Sequence[google.cloud.documentai_v1beta3.types.Document.Style]):
Styles for the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
- pages (Sequence[~.document.Document.Page]):
+ pages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page]):
Visual page layout for the
[Document][google.cloud.documentai.v1beta3.Document].
- entities (Sequence[~.document.Document.Entity]):
+ entities (Sequence[google.cloud.documentai_v1beta3.types.Document.Entity]):
A list of entities detected on
[Document.text][google.cloud.documentai.v1beta3.Document.text].
For document shards, entities in this list may cross shard
boundaries.
- entity_relations (Sequence[~.document.Document.EntityRelation]):
+ entity_relations (Sequence[google.cloud.documentai_v1beta3.types.Document.EntityRelation]):
Relationship among
[Document.entities][google.cloud.documentai.v1beta3.Document.entities].
- translations (Sequence[~.document.Document.Translation]):
- A list of translations on
- [Document.text][google.cloud.documentai.v1beta3.Document.text].
- For document shards, translations in this list may cross
- shard boundaries.
- text_changes (Sequence[~.document.Document.TextChange]):
+ text_changes (Sequence[google.cloud.documentai_v1beta3.types.Document.TextChange]):
A list of text corrections made to [Document.text]. This is
usually used for annotating corrections to OCR mistakes.
Text changes for a given revision may not overlap with each
other.
- shard_info (~.document.Document.ShardInfo):
+ shard_info (google.cloud.documentai_v1beta3.types.Document.ShardInfo):
Information about the sharding if this
document is sharded part of a larger document.
If the document is not sharded, this message is
not specified.
- error (~.status.Status):
+ error (google.rpc.status_pb2.Status):
Any error that occurred while processing this
document.
- revisions (Sequence[~.document.Document.Revision]):
+ revisions (Sequence[google.cloud.documentai_v1beta3.types.Document.Revision]):
Revision history of this document.
"""
@@ -123,12 +118,12 @@ class Style(proto.Message):
CSS conventions as much as possible.
Attributes:
- text_anchor (~.document.Document.TextAnchor):
+ text_anchor (google.cloud.documentai_v1beta3.types.Document.TextAnchor):
Text anchor indexing into the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
- color (~.gt_color.Color):
+ color (google.type.color_pb2.Color):
Text color.
- background_color (~.gt_color.Color):
+ background_color (google.type.color_pb2.Color):
Text background color.
font_weight (str):
Font weight. Possible values are normal, bold, bolder, and
@@ -139,7 +134,7 @@ class Style(proto.Message):
text_decoration (str):
Text decoration. Follows CSS standard.
https://www.w3schools.com/cssref/pr_text_text-decoration.asp
- font_size (~.document.Document.Style.FontSize):
+ font_size (google.cloud.documentai_v1beta3.types.Document.Style.FontSize):
Font size.
"""
@@ -187,46 +182,46 @@ class Page(proto.Message):
Useful when a page is taken out of a
[Document][google.cloud.documentai.v1beta3.Document] for
individual processing.
- image (~.document.Document.Page.Image):
+ image (google.cloud.documentai_v1beta3.types.Document.Page.Image):
Rendered image for this page. This image is
preprocessed to remove any skew, rotation, and
distortions such that the annotation bounding
boxes can be upright and axis-aligned.
- transforms (Sequence[~.document.Document.Page.Matrix]):
+ transforms (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Matrix]):
Transformation matrices that were applied to the original
document image to produce
[Page.image][google.cloud.documentai.v1beta3.Document.Page.image].
- dimension (~.document.Document.Page.Dimension):
+ dimension (google.cloud.documentai_v1beta3.types.Document.Page.Dimension):
Physical dimension of the page.
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for the page.
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
- blocks (Sequence[~.document.Document.Page.Block]):
+ blocks (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Block]):
A list of visually detected text blocks on
the page. A block has a set of lines (collected
into paragraphs) that have a common line-spacing
and orientation.
- paragraphs (Sequence[~.document.Document.Page.Paragraph]):
+ paragraphs (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Paragraph]):
A list of visually detected text paragraphs
on the page. A collection of lines that a human
would perceive as a paragraph.
- lines (Sequence[~.document.Document.Page.Line]):
+ lines (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Line]):
A list of visually detected text lines on the
page. A collection of tokens that a human would
perceive as a line.
- tokens (Sequence[~.document.Document.Page.Token]):
+ tokens (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Token]):
A list of visually detected tokens on the
page.
- visual_elements (Sequence[~.document.Document.Page.VisualElement]):
+ visual_elements (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.VisualElement]):
A list of detected non-text visual elements
e.g. checkbox, signature etc. on the page.
- tables (Sequence[~.document.Document.Page.Table]):
+ tables (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Table]):
A list of visually detected tables on the
page.
- form_fields (Sequence[~.document.Document.Page.FormField]):
+ form_fields (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.FormField]):
A list of visually detected form fields on
the page.
"""
@@ -302,7 +297,7 @@ class Layout(proto.Message):
r"""Visual element describing a layout unit on a page.
Attributes:
- text_anchor (~.document.Document.TextAnchor):
+ text_anchor (google.cloud.documentai_v1beta3.types.Document.TextAnchor):
Text anchor indexing into the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
confidence (float):
@@ -311,10 +306,10 @@ class Layout(proto.Message):
within context of the object this layout is for. e.g.
confidence can be for a single token, a table, a visual
element, etc. depending on context. Range [0, 1].
- bounding_poly (~.geometry.BoundingPoly):
+ bounding_poly (google.cloud.documentai_v1beta3.types.BoundingPoly):
The bounding polygon for the
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout].
- orientation (~.document.Document.Page.Layout.Orientation):
+ orientation (google.cloud.documentai_v1beta3.types.Document.Page.Layout.Orientation):
Detected orientation for the
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout].
"""
@@ -346,14 +341,14 @@ class Block(proto.Message):
have a common line-spacing and orientation.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[Block][google.cloud.documentai.v1beta3.Document.Page.Block].
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
- provenance (~.document.Document.Provenance):
+ provenance (google.cloud.documentai_v1beta3.types.Document.Provenance):
The history of this annotation.
"""
@@ -374,14 +369,14 @@ class Paragraph(proto.Message):
paragraph.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[Paragraph][google.cloud.documentai.v1beta3.Document.Page.Paragraph].
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
- provenance (~.document.Document.Provenance):
+ provenance (google.cloud.documentai_v1beta3.types.Document.Provenance):
The history of this annotation.
"""
@@ -403,14 +398,14 @@ class Line(proto.Message):
etc.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[Line][google.cloud.documentai.v1beta3.Document.Page.Line].
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
- provenance (~.document.Document.Provenance):
+ provenance (google.cloud.documentai_v1beta3.types.Document.Provenance):
The history of this annotation.
"""
@@ -430,17 +425,17 @@ class Token(proto.Message):
r"""A detected token.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[Token][google.cloud.documentai.v1beta3.Document.Page.Token].
- detected_break (~.document.Document.Page.Token.DetectedBreak):
+ detected_break (google.cloud.documentai_v1beta3.types.Document.Page.Token.DetectedBreak):
Detected break at the end of a
[Token][google.cloud.documentai.v1beta3.Document.Page.Token].
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
- provenance (~.document.Document.Provenance):
+ provenance (google.cloud.documentai_v1beta3.types.Document.Provenance):
The history of this annotation.
"""
@@ -449,7 +444,7 @@ class DetectedBreak(proto.Message):
[Token][google.cloud.documentai.v1beta3.Document.Page.Token].
Attributes:
- type_ (~.document.Document.Page.Token.DetectedBreak.Type):
+ type_ (google.cloud.documentai_v1beta3.types.Document.Page.Token.DetectedBreak.Type):
Detected break type.
"""
@@ -485,14 +480,14 @@ class VisualElement(proto.Message):
etc. on the page.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[VisualElement][google.cloud.documentai.v1beta3.Document.Page.VisualElement].
type_ (str):
Type of the
[VisualElement][google.cloud.documentai.v1beta3.Document.Page.VisualElement].
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
"""
@@ -511,15 +506,15 @@ class Table(proto.Message):
r"""A table representation similar to HTML table structure.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[Table][google.cloud.documentai.v1beta3.Document.Page.Table].
- header_rows (Sequence[~.document.Document.Page.Table.TableRow]):
+ header_rows (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Table.TableRow]):
Header rows of the table.
- body_rows (Sequence[~.document.Document.Page.Table.TableRow]):
+ body_rows (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Table.TableRow]):
Body rows of the table.
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
"""
@@ -528,7 +523,7 @@ class TableRow(proto.Message):
r"""A row of table cells.
Attributes:
- cells (Sequence[~.document.Document.Page.Table.TableCell]):
+ cells (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.Table.TableCell]):
Cells that make up this row.
"""
@@ -540,7 +535,7 @@ class TableCell(proto.Message):
r"""A cell representation inside the table.
Attributes:
- layout (~.document.Document.Page.Layout):
+ layout (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for
[TableCell][google.cloud.documentai.v1beta3.Document.Page.Table.TableCell].
@@ -548,7 +543,7 @@ class TableCell(proto.Message):
How many rows this cell spans.
col_span (int):
How many columns this cell spans.
- detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages together with
confidence.
"""
@@ -585,21 +580,21 @@ class FormField(proto.Message):
r"""A form field detected on the page.
Attributes:
- field_name (~.document.Document.Page.Layout):
+ field_name (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for the
[FormField][google.cloud.documentai.v1beta3.Document.Page.FormField]
name. e.g. ``Address``, ``Email``, ``Grand total``,
``Phone number``, etc.
- field_value (~.document.Document.Page.Layout):
+ field_value (google.cloud.documentai_v1beta3.types.Document.Page.Layout):
[Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
for the
[FormField][google.cloud.documentai.v1beta3.Document.Page.FormField]
value.
- name_detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ name_detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages for name
together with confidence.
- value_detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ value_detected_languages (Sequence[google.cloud.documentai_v1beta3.types.Document.Page.DetectedLanguage]):
A list of detected languages for value
together with confidence.
value_type (str):
@@ -696,36 +691,38 @@ class Entity(proto.Message):
person, an organization, or location.
Attributes:
- text_anchor (~.document.Document.TextAnchor):
- Provenance of the entity. Text anchor indexing into the
+ text_anchor (google.cloud.documentai_v1beta3.types.Document.TextAnchor):
+ Optional. Provenance of the entity. Text anchor indexing
+ into the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
type_ (str):
Entity type from a schema e.g. ``Address``.
mention_text (str):
- Text value in the document e.g. ``1600 Amphitheatre Pkwy``.
+ Optional. Text value in the document e.g.
+ ``1600 Amphitheatre Pkwy``.
mention_id (str):
- Deprecated. Use ``id`` field instead.
+ Optional. Deprecated. Use ``id`` field instead.
confidence (float):
Optional. Confidence of detected Schema entity. Range [0,
1].
- page_anchor (~.document.Document.PageAnchor):
+ page_anchor (google.cloud.documentai_v1beta3.types.Document.PageAnchor):
Optional. Represents the provenance of this
entity wrt. the location on the page where it
was found.
id (str):
- Canonical id. This will be a unique value in
- the entity list for this document.
- normalized_value (~.document.Document.Entity.NormalizedValue):
+ Optional. Canonical id. This will be a unique
+ value in the entity list for this document.
+ normalized_value (google.cloud.documentai_v1beta3.types.Document.Entity.NormalizedValue):
Optional. Normalized entity value. Absent if
the extracted value could not be converted or
the type (e.g. address) is not supported for
certain parsers. This field is also only
populated for certain supported document types.
- properties (Sequence[~.document.Document.Entity]):
+ properties (Sequence[google.cloud.documentai_v1beta3.types.Document.Entity]):
Optional. Entities can be nested to form a
hierarchical data structure representing the
content in the document.
- provenance (~.document.Document.Provenance):
+ provenance (google.cloud.documentai_v1beta3.types.Document.Provenance):
Optional. The history of this annotation.
redacted (bool):
Optional. Whether the entity will be redacted
@@ -736,25 +733,23 @@ class NormalizedValue(proto.Message):
r"""Parsed and normalized entity value.
Attributes:
- money_value (~.money.Money):
+ money_value (google.type.money_pb2.Money):
Money value. See also:
- https:
- github.com/googleapis/googleapis/blob/master/google/type/money.proto
- date_value (~.date.Date):
+ https://github.com/googleapis/googleapis/blob/master/google/type/money.proto
+ date_value (google.type.date_pb2.Date):
Date value. Includes year, month, day. See
also:
- https:
- github.com/googleapis/googleapis/blob/master/google/type/date.proto
- datetime_value (~.datetime.DateTime):
+ https://github.com/googleapis/googleapis/blob/master/google/type/date.proto
+ datetime_value (google.type.datetime_pb2.DateTime):
DateTime value. Includes date, time, and
timezone. See also:
- https:
- github.com/googleapis/googleapis/blob/master/google/type/datetime.proto
- address_value (~.postal_address.PostalAddress):
+ https://github.com/googleapis/googleapis/blob/master/google/type/datetime.proto
+ address_value (google.type.postal_address_pb2.PostalAddress):
Postal address. See also:
-
- https:
- github.com/googleapis/googleapis/blob/master/google/type/postal_address.proto
+ https://github.com/googleapis/googleapis/blob/master/google/type/postal_address.proto
+ boolean_value (bool):
+ Boolean value. Can be used for entities with
+ binary values, or for checkboxes.
text (str):
Required. Normalized entity value stored as a string. This
field is populated for supported document type (e.g.
@@ -791,6 +786,8 @@ class NormalizedValue(proto.Message):
message=postal_address.PostalAddress,
)
+ boolean_value = proto.Field(proto.BOOL, number=6, oneof="structured_value")
+
text = proto.Field(proto.STRING, number=1)
text_anchor = proto.Field(
@@ -844,44 +841,12 @@ class EntityRelation(proto.Message):
relation = proto.Field(proto.STRING, number=3)
- class Translation(proto.Message):
- r"""A translation of the text segment.
-
- Attributes:
- text_anchor (~.document.Document.TextAnchor):
- Provenance of the translation. Text anchor indexing into the
- [Document.text][google.cloud.documentai.v1beta3.Document.text].
- There can only be a single ``TextAnchor.text_segments``
- element. If the start and end index of the text segment are
- the same, the text change is inserted before that index.
- language_code (str):
- The BCP-47 language code, such as "en-US" or "sr-Latn". For
- more information, see
- http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
- translated_text (str):
- Text translated into the target language.
- provenance (Sequence[~.document.Document.Provenance]):
- The history of this annotation.
- """
-
- text_anchor = proto.Field(
- proto.MESSAGE, number=1, message="Document.TextAnchor",
- )
-
- language_code = proto.Field(proto.STRING, number=2)
-
- translated_text = proto.Field(proto.STRING, number=3)
-
- provenance = proto.RepeatedField(
- proto.MESSAGE, number=4, message="Document.Provenance",
- )
-
class TextAnchor(proto.Message):
r"""Text reference indexing into the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
Attributes:
- text_segments (Sequence[~.document.Document.TextAnchor.TextSegment]):
+ text_segments (Sequence[google.cloud.documentai_v1beta3.types.Document.TextAnchor.TextSegment]):
The text segments from the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
content (str):
@@ -924,7 +889,7 @@ class PageAnchor(proto.Message):
polygons and optionally reference specific layout element types.
Attributes:
- page_refs (Sequence[~.document.Document.PageAnchor.PageRef]):
+ page_refs (Sequence[google.cloud.documentai_v1beta3.types.Document.PageAnchor.PageRef]):
One or more references to visual page
elements
"""
@@ -937,15 +902,16 @@ class PageRef(proto.Message):
page (int):
Required. Index into the
[Document.pages][google.cloud.documentai.v1beta3.Document.pages]
- element
- layout_type (~.document.Document.PageAnchor.PageRef.LayoutType):
+ element, for example using [Document.pages][page_refs.page]
+ to locate the related page element.
+ layout_type (google.cloud.documentai_v1beta3.types.Document.PageAnchor.PageRef.LayoutType):
Optional. The type of the layout element that
is being referenced if any.
layout_id (str):
Optional. Deprecated. Use
[PageRef.bounding_poly][google.cloud.documentai.v1beta3.Document.PageAnchor.PageRef.bounding_poly]
instead.
- bounding_poly (~.geometry.BoundingPoly):
+ bounding_poly (google.cloud.documentai_v1beta3.types.BoundingPoly):
Optional. Identifies the bounding polygon of
a layout element on the page.
"""
@@ -988,10 +954,10 @@ class Provenance(proto.Message):
id (int):
The Id of this operation. Needs to be unique
within the scope of the revision.
- parents (Sequence[~.document.Document.Provenance.Parent]):
+ parents (Sequence[google.cloud.documentai_v1beta3.types.Document.Provenance.Parent]):
References to the original elements that are
replaced.
- type_ (~.document.Document.Provenance.OperationType):
+ type_ (google.cloud.documentai_v1beta3.types.Document.Provenance.OperationType):
The type of provenance operation.
"""
@@ -1005,6 +971,7 @@ class OperationType(proto.Enum):
REPLACE = 3
EVAL_REQUESTED = 4
EVAL_APPROVED = 5
+ EVAL_SKIPPED = 6
class Parent(proto.Message):
r"""Structure for referencing parent provenances. When an
@@ -1052,9 +1019,9 @@ class Revision(proto.Message):
The revisions that this revision is based on. This can
include one or more parent (when documents are merged.) This
field represents the index into the ``revisions`` field.
- create_time (~.timestamp.Timestamp):
+ create_time (google.protobuf.timestamp_pb2.Timestamp):
The time that the revision was created.
- human_review (~.document.Document.Revision.HumanReview):
+ human_review (google.cloud.documentai_v1beta3.types.Document.Revision.HumanReview):
Human Review information of this revision.
"""
@@ -1093,7 +1060,7 @@ class TextChange(proto.Message):
r"""This message is used for text changes aka. OCR corrections.
Attributes:
- text_anchor (~.document.Document.TextAnchor):
+ text_anchor (google.cloud.documentai_v1beta3.types.Document.TextAnchor):
Provenance of the correction. Text anchor indexing into the
[Document.text][google.cloud.documentai.v1beta3.Document.text].
There can only be a single ``TextAnchor.text_segments``
@@ -1102,7 +1069,7 @@ class TextChange(proto.Message):
changed_text (str):
The text that replaces the text identified in the
``text_anchor``.
- provenance (Sequence[~.document.Document.Provenance]):
+ provenance (Sequence[google.cloud.documentai_v1beta3.types.Document.Provenance]):
The history of this annotation.
"""
@@ -1134,8 +1101,6 @@ class TextChange(proto.Message):
proto.MESSAGE, number=8, message=EntityRelation,
)
- translations = proto.RepeatedField(proto.MESSAGE, number=12, message=Translation,)
-
text_changes = proto.RepeatedField(proto.MESSAGE, number=14, message=TextChange,)
shard_info = proto.Field(proto.MESSAGE, number=9, message=ShardInfo,)
diff --git a/google/cloud/documentai_v1beta3/types/document_io.py b/google/cloud/documentai_v1beta3/types/document_io.py
new file mode 100644
index 00000000..37928c86
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/types/document_io.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import proto # type: ignore
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.documentai.v1beta3",
+ manifest={
+ "RawDocument",
+ "GcsDocument",
+ "GcsDocuments",
+ "GcsPrefix",
+ "BatchDocumentsInputConfig",
+ "DocumentOutputConfig",
+ },
+)
+
+
+class RawDocument(proto.Message):
+ r"""Payload message of raw document content (bytes).
+
+ Attributes:
+ content (bytes):
+ Inline document content.
+ mime_type (str):
+ An IANA MIME type (RFC6838) indicating the nature and format
+ of the [content].
+ """
+
+ content = proto.Field(proto.BYTES, number=1)
+
+ mime_type = proto.Field(proto.STRING, number=2)
+
+
+class GcsDocument(proto.Message):
+ r"""Specifies a document stored on Cloud Storage.
+
+ Attributes:
+ gcs_uri (str):
+ The Cloud Storage object uri.
+ mime_type (str):
+ An IANA MIME type (RFC6838) of the content.
+ """
+
+ gcs_uri = proto.Field(proto.STRING, number=1)
+
+ mime_type = proto.Field(proto.STRING, number=2)
+
+
+class GcsDocuments(proto.Message):
+ r"""Specifies a set of documents on Cloud Storage.
+
+ Attributes:
+ documents (Sequence[google.cloud.documentai_v1beta3.types.GcsDocument]):
+ The list of documents.
+ """
+
+ documents = proto.RepeatedField(proto.MESSAGE, number=1, message="GcsDocument",)
+
+
+class GcsPrefix(proto.Message):
+ r"""Specifies all documents on Cloud Storage with a common
+ prefix.
+
+ Attributes:
+ gcs_uri_prefix (str):
+ The URI prefix.
+ """
+
+ gcs_uri_prefix = proto.Field(proto.STRING, number=1)
+
+
+class BatchDocumentsInputConfig(proto.Message):
+ r"""The common config to specify a set of documents used as
+ input.
+
+ Attributes:
+ gcs_prefix (google.cloud.documentai_v1beta3.types.GcsPrefix):
+ The set of documents that match the specified Cloud Storage
+ [gcs_prefix].
+ gcs_documents (google.cloud.documentai_v1beta3.types.GcsDocuments):
+ The set of documents individually specified
+ on Cloud Storage.
+ """
+
+ gcs_prefix = proto.Field(
+ proto.MESSAGE, number=1, oneof="source", message="GcsPrefix",
+ )
+
+ gcs_documents = proto.Field(
+ proto.MESSAGE, number=2, oneof="source", message="GcsDocuments",
+ )
+
+
+class DocumentOutputConfig(proto.Message):
+ r"""Config that controls the output of documents. All documents
+ will be written as a JSON file.
+
+ Attributes:
+ gcs_output_config (google.cloud.documentai_v1beta3.types.DocumentOutputConfig.GcsOutputConfig):
+ Output config to write the results to Cloud
+ Storage.
+ """
+
+ class GcsOutputConfig(proto.Message):
+ r"""The configuration used when outputting documents.
+
+ Attributes:
+ gcs_uri (str):
+ The Cloud Storage uri (a directory) of the
+ output.
+ """
+
+ gcs_uri = proto.Field(proto.STRING, number=1)
+
+ gcs_output_config = proto.Field(
+ proto.MESSAGE, number=1, oneof="destination", message=GcsOutputConfig,
+ )
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/documentai_v1beta3/types/document_processor_service.py b/google/cloud/documentai_v1beta3/types/document_processor_service.py
index 7d235c25..3e025e8a 100644
--- a/google/cloud/documentai_v1beta3/types/document_processor_service.py
+++ b/google/cloud/documentai_v1beta3/types/document_processor_service.py
@@ -19,6 +19,7 @@
from google.cloud.documentai_v1beta3.types import document as gcd_document
+from google.cloud.documentai_v1beta3.types import document_io
from google.protobuf import timestamp_pb2 as timestamp # type: ignore
from google.rpc import status_pb2 as gr_status # type: ignore
@@ -27,6 +28,7 @@
package="google.cloud.documentai.v1beta3",
manifest={
"ProcessRequest",
+ "HumanReviewStatus",
"ProcessResponse",
"BatchProcessRequest",
"BatchProcessResponse",
@@ -34,6 +36,7 @@
"ReviewDocumentRequest",
"ReviewDocumentResponse",
"ReviewDocumentOperationMetadata",
+ "CommonOperationMetadata",
},
)
@@ -42,9 +45,13 @@ class ProcessRequest(proto.Message):
r"""Request message for the process document method.
Attributes:
+ inline_document (google.cloud.documentai_v1beta3.types.Document):
+ An inline document proto.
+ raw_document (google.cloud.documentai_v1beta3.types.RawDocument):
+ A raw document content (bytes).
name (str):
Required. The processor resource name.
- document (~.gcd_document.Document):
+ document (google.cloud.documentai_v1beta3.types.Document):
The document payload, the [content] and [mime_type] fields
must be set.
skip_human_review (bool):
@@ -52,6 +59,14 @@ class ProcessRequest(proto.Message):
skipped for this request. Default to false.
"""
+ inline_document = proto.Field(
+ proto.MESSAGE, number=4, oneof="source", message=gcd_document.Document,
+ )
+
+ raw_document = proto.Field(
+ proto.MESSAGE, number=5, oneof="source", message=document_io.RawDocument,
+ )
+
name = proto.Field(proto.STRING, number=1)
document = proto.Field(proto.MESSAGE, number=2, message=gcd_document.Document,)
@@ -59,11 +74,44 @@ class ProcessRequest(proto.Message):
skip_human_review = proto.Field(proto.BOOL, number=3)
+class HumanReviewStatus(proto.Message):
+ r"""The status of human review on a processed document.
+
+ Attributes:
+ state (google.cloud.documentai_v1beta3.types.HumanReviewStatus.State):
+ The state of human review on the processing
+ request.
+ state_message (str):
+ A message providing more details about the
+ human review state.
+ human_review_operation (str):
+ The name of the operation triggered by the processed
+ document. This field is populated only when the [state] is
+ [HUMAN_REVIEW_IN_PROGRESS]. It has the same response type
+ and metadata as the long running operation returned by
+ [ReviewDocument] method.
+ """
+
+ class State(proto.Enum):
+ r"""The final state of human review on a processed document."""
+ STATE_UNSPECIFIED = 0
+ SKIPPED = 1
+ VALIDATION_PASSED = 2
+ IN_PROGRESS = 3
+ ERROR = 4
+
+ state = proto.Field(proto.ENUM, number=1, enum=State,)
+
+ state_message = proto.Field(proto.STRING, number=2)
+
+ human_review_operation = proto.Field(proto.STRING, number=3)
+
+
class ProcessResponse(proto.Message):
r"""Response message for the process document method.
Attributes:
- document (~.gcd_document.Document):
+ document (google.cloud.documentai_v1beta3.types.Document):
The document payload, will populate fields
based on the processor's behavior.
human_review_operation (str):
@@ -73,12 +121,19 @@ class ProcessResponse(proto.Message):
has the same response type and metadata as the
long running operation returned by
ReviewDocument method.
+ human_review_status (google.cloud.documentai_v1beta3.types.HumanReviewStatus):
+ The status of human review on the processed
+ document.
"""
document = proto.Field(proto.MESSAGE, number=1, message=gcd_document.Document,)
human_review_operation = proto.Field(proto.STRING, number=2)
+ human_review_status = proto.Field(
+ proto.MESSAGE, number=3, message="HumanReviewStatus",
+ )
+
class BatchProcessRequest(proto.Message):
r"""Request message for batch process document method.
@@ -86,11 +141,18 @@ class BatchProcessRequest(proto.Message):
Attributes:
name (str):
Required. The processor resource name.
- input_configs (Sequence[~.document_processor_service.BatchProcessRequest.BatchInputConfig]):
+ input_configs (Sequence[google.cloud.documentai_v1beta3.types.BatchProcessRequest.BatchInputConfig]):
The input config for each single document in
the batch process.
- output_config (~.document_processor_service.BatchProcessRequest.BatchOutputConfig):
+ output_config (google.cloud.documentai_v1beta3.types.BatchProcessRequest.BatchOutputConfig):
+ The overall output config for batch process.
+ input_documents (google.cloud.documentai_v1beta3.types.BatchDocumentsInputConfig):
+ The input documents for batch process.
+ document_output_config (google.cloud.documentai_v1beta3.types.DocumentOutputConfig):
The overall output config for batch process.
+ skip_human_review (bool):
+ Whether Human Review feature should be
+ skipped for this request. Default to false.
"""
class BatchInputConfig(proto.Message):
@@ -130,6 +192,16 @@ class BatchOutputConfig(proto.Message):
output_config = proto.Field(proto.MESSAGE, number=3, message=BatchOutputConfig,)
+ input_documents = proto.Field(
+ proto.MESSAGE, number=5, message=document_io.BatchDocumentsInputConfig,
+ )
+
+ document_output_config = proto.Field(
+ proto.MESSAGE, number=6, message=document_io.DocumentOutputConfig,
+ )
+
+ skip_human_review = proto.Field(proto.BOOL, number=4)
+
class BatchProcessResponse(proto.Message):
r"""Response message for batch process document method."""
@@ -139,17 +211,17 @@ class BatchProcessMetadata(proto.Message):
r"""The long running operation metadata for batch process method.
Attributes:
- state (~.document_processor_service.BatchProcessMetadata.State):
+ state (google.cloud.documentai_v1beta3.types.BatchProcessMetadata.State):
The state of the current batch processing.
state_message (str):
A message providing more details about the
current state of processing. For example, the
error message if the operation is failed.
- create_time (~.timestamp.Timestamp):
+ create_time (google.protobuf.timestamp_pb2.Timestamp):
The creation time of the operation.
- update_time (~.timestamp.Timestamp):
+ update_time (google.protobuf.timestamp_pb2.Timestamp):
The last update time of the operation.
- individual_process_statuses (Sequence[~.document_processor_service.BatchProcessMetadata.IndividualProcessStatus]):
+ individual_process_statuses (Sequence[google.cloud.documentai_v1beta3.types.BatchProcessMetadata.IndividualProcessStatus]):
The list of response details of each
document.
"""
@@ -175,7 +247,7 @@ class IndividualProcessStatus(proto.Message):
batch process is started by take snapshot of that document,
since a user can move or change that document during the
process.
- status (~.gr_status.Status):
+ status (google.rpc.status_pb2.Status):
The status of the processing of the document.
output_gcs_destination (str):
The output_gcs_destination (in the request as
@@ -188,6 +260,9 @@ class IndividualProcessStatus(proto.Message):
has the same response type and metadata as the
long running operation returned by
ReviewDocument method.
+ human_review_status (google.cloud.documentai_v1beta3.types.HumanReviewStatus):
+ The status of human review on the processed
+ document.
"""
input_gcs_source = proto.Field(proto.STRING, number=1)
@@ -198,6 +273,10 @@ class IndividualProcessStatus(proto.Message):
human_review_operation = proto.Field(proto.STRING, number=4)
+ human_review_status = proto.Field(
+ proto.MESSAGE, number=5, message="HumanReviewStatus",
+ )
+
state = proto.Field(proto.ENUM, number=1, enum=State,)
state_message = proto.Field(proto.STRING, number=2)
@@ -215,14 +294,20 @@ class ReviewDocumentRequest(proto.Message):
r"""Request message for review document method.
Attributes:
+ inline_document (google.cloud.documentai_v1beta3.types.Document):
+ An inline document proto.
human_review_config (str):
Required. The resource name of the
HumanReviewConfig that the document will be
reviewed with.
- document (~.gcd_document.Document):
+ document (google.cloud.documentai_v1beta3.types.Document):
The document that needs human review.
"""
+ inline_document = proto.Field(
+ proto.MESSAGE, number=4, oneof="source", message=gcd_document.Document,
+ )
+
human_review_config = proto.Field(proto.STRING, number=1)
document = proto.Field(proto.MESSAGE, number=2, message=gcd_document.Document,)
@@ -245,15 +330,55 @@ class ReviewDocumentOperationMetadata(proto.Message):
method.
Attributes:
- state (~.document_processor_service.ReviewDocumentOperationMetadata.State):
+ state (google.cloud.documentai_v1beta3.types.ReviewDocumentOperationMetadata.State):
Used only when Operation.done is false.
state_message (str):
A message providing more details about the
current state of processing. For example, the
error message if the operation is failed.
- create_time (~.timestamp.Timestamp):
+ create_time (google.protobuf.timestamp_pb2.Timestamp):
+ The creation time of the operation.
+ update_time (google.protobuf.timestamp_pb2.Timestamp):
+ The last update time of the operation.
+ common_metadata (google.cloud.documentai_v1beta3.types.CommonOperationMetadata):
+ The basic metadata of the long running
+ operation.
+ """
+
+ class State(proto.Enum):
+ r"""State of the longrunning operation."""
+ STATE_UNSPECIFIED = 0
+ RUNNING = 1
+ CANCELLING = 2
+ SUCCEEDED = 3
+ FAILED = 4
+ CANCELLED = 5
+
+ state = proto.Field(proto.ENUM, number=1, enum=State,)
+
+ state_message = proto.Field(proto.STRING, number=2)
+
+ create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,)
+
+ update_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp,)
+
+ common_metadata = proto.Field(
+ proto.MESSAGE, number=5, message="CommonOperationMetadata",
+ )
+
+
+class CommonOperationMetadata(proto.Message):
+ r"""The common metadata for long running operations.
+
+ Attributes:
+ state (google.cloud.documentai_v1beta3.types.CommonOperationMetadata.State):
+ The state of the operation.
+ state_message (str):
+ A message providing more details about the
+ current state of processing.
+ create_time (google.protobuf.timestamp_pb2.Timestamp):
The creation time of the operation.
- update_time (~.timestamp.Timestamp):
+ update_time (google.protobuf.timestamp_pb2.Timestamp):
The last update time of the operation.
"""
diff --git a/google/cloud/documentai_v1beta3/types/geometry.py b/google/cloud/documentai_v1beta3/types/geometry.py
index e87b87c7..53c3b9b0 100644
--- a/google/cloud/documentai_v1beta3/types/geometry.py
+++ b/google/cloud/documentai_v1beta3/types/geometry.py
@@ -62,16 +62,16 @@ class BoundingPoly(proto.Message):
r"""A bounding polygon for the detected image annotation.
Attributes:
- vertices (Sequence[~.geometry.Vertex]):
+ vertices (Sequence[google.cloud.documentai_v1beta3.types.Vertex]):
The bounding polygon vertices.
- normalized_vertices (Sequence[~.geometry.NormalizedVertex]):
+ normalized_vertices (Sequence[google.cloud.documentai_v1beta3.types.NormalizedVertex]):
The bounding polygon normalized vertices.
"""
- vertices = proto.RepeatedField(proto.MESSAGE, number=1, message=Vertex,)
+ vertices = proto.RepeatedField(proto.MESSAGE, number=1, message="Vertex",)
normalized_vertices = proto.RepeatedField(
- proto.MESSAGE, number=2, message=NormalizedVertex,
+ proto.MESSAGE, number=2, message="NormalizedVertex",
)
diff --git a/noxfile.py b/noxfile.py
index e446dd8d..ae8392be 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -18,6 +18,7 @@
from __future__ import absolute_import
import os
+import pathlib
import shutil
import nox
@@ -28,7 +29,23 @@
DEFAULT_PYTHON_VERSION = "3.8"
SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"]
-UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8"]
+UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"]
+
+CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
+
+# 'docfx' is excluded since it only needs to run in 'docs-presubmit'
+nox.options.sessions = [
+ "unit",
+ "system",
+ "cover",
+ "lint",
+ "lint_setup_py",
+ "blacken",
+ "docs",
+]
+
+# Error if a python version is missing
+nox.options.error_on_missing_interpreters = True
@nox.session(python=DEFAULT_PYTHON_VERSION)
@@ -70,18 +87,23 @@ def lint_setup_py(session):
def default(session):
# Install all test dependencies, then install this package in-place.
- session.install("asyncmock", "pytest-asyncio")
- session.install("mock", "pytest", "pytest-cov")
- session.install("-e", ".")
+ constraints_path = str(
+ CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
+ )
+ session.install("asyncmock", "pytest-asyncio", "-c", constraints_path)
+
+ session.install("mock", "pytest", "pytest-cov", "-c", constraints_path)
+
+ session.install("-e", ".", "-c", constraints_path)
# Run py.test against the unit tests.
session.run(
"py.test",
"--quiet",
- "--cov=google.cloud.documentai",
- "--cov=google.cloud",
- "--cov=tests.unit",
+ f"--junitxml=unit_{session.python}_sponge_log.xml",
+ "--cov=google/cloud",
+ "--cov=tests/unit",
"--cov-append",
"--cov-config=.coveragerc",
"--cov-report=",
@@ -100,6 +122,9 @@ def unit(session):
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)
def system(session):
"""Run the system test suite."""
+ constraints_path = str(
+ CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
+ )
system_test_path = os.path.join("tests", "system.py")
system_test_folder_path = os.path.join("tests", "system")
@@ -109,6 +134,9 @@ def system(session):
# Sanity check: Only run tests if the environment variable is set.
if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""):
session.skip("Credentials must be set via environment variable")
+ # Install pyopenssl for mTLS testing.
+ if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true":
+ session.install("pyopenssl")
system_test_exists = os.path.exists(system_test_path)
system_test_folder_exists = os.path.exists(system_test_folder_path)
@@ -121,16 +149,26 @@ def system(session):
# Install all test dependencies, then install this package into the
# virtualenv's dist-packages.
- session.install(
- "mock", "pytest", "google-cloud-testutils",
- )
- session.install("-e", ".")
+ session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path)
+ session.install("-e", ".", "-c", constraints_path)
# Run py.test against the system tests.
if system_test_exists:
- session.run("py.test", "--quiet", system_test_path, *session.posargs)
+ session.run(
+ "py.test",
+ "--quiet",
+ f"--junitxml=system_{session.python}_sponge_log.xml",
+ system_test_path,
+ *session.posargs,
+ )
if system_test_folder_exists:
- session.run("py.test", "--quiet", system_test_folder_path, *session.posargs)
+ session.run(
+ "py.test",
+ "--quiet",
+ f"--junitxml=system_{session.python}_sponge_log.xml",
+ system_test_folder_path,
+ *session.posargs,
+ )
@nox.session(python=DEFAULT_PYTHON_VERSION)
@@ -141,7 +179,7 @@ def cover(session):
test runs (not system test runs), and then erases coverage data.
"""
session.install("coverage", "pytest-cov")
- session.run("coverage", "report", "--show-missing", "--fail-under=100")
+ session.run("coverage", "report", "--show-missing", "--fail-under=99")
session.run("coverage", "erase")
diff --git a/renovate.json b/renovate.json
index 4fa94931..f08bc22c 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,5 +1,6 @@
{
"extends": [
"config:base", ":preserveSemverRanges"
- ]
+ ],
+ "ignorePaths": [".pre-commit-config.yaml"]
}
diff --git a/samples/__init__.py b/samples/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/samples/snippets/__init__.py b/samples/snippets/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/samples/snippets/batch_parse_form_v1beta2.py b/samples/snippets/batch_parse_form_v1beta2.py
new file mode 100644
index 00000000..ae60fd63
--- /dev/null
+++ b/samples/snippets/batch_parse_form_v1beta2.py
@@ -0,0 +1,100 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# [START documentai_batch_parse_form_beta]
+import re
+
+from google.cloud import documentai_v1beta2 as documentai
+from google.cloud import storage
+
+
+def batch_parse_form(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/form.pdf",
+ destination_uri="gs://your-bucket-id/path/to/save/results/",
+ timeout=90
+):
+ """Parse a form"""
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # where to write results
+ output_config = documentai.types.OutputConfig(
+ gcs_destination=documentai.types.GcsDestination(uri=destination_uri),
+ pages_per_shard=1, # Map one doc page to one output page
+ )
+
+ # Improve form parsing results by providing key-value pair hints.
+ # For each key hint, key is text that is likely to appear in the
+ # document as a form field name (i.e. "DOB").
+ # Value types are optional, but can be one or more of:
+ # ADDRESS, LOCATION, ORGANIZATION, PERSON, PHONE_NUMBER, ID,
+ # NUMBER, EMAIL, PRICE, TERMS, DATE, NAME
+ key_value_pair_hints = [
+ documentai.types.KeyValuePairHint(
+ key="Emergency Contact", value_types=["NAME"]
+ ),
+ documentai.types.KeyValuePairHint(key="Referred By"),
+ ]
+
+ # Setting enabled=True enables form extraction
+ form_extraction_params = documentai.types.FormExtractionParams(
+ enabled=True, key_value_pair_hints=key_value_pair_hints
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ input_config=input_config,
+ output_config=output_config,
+ form_extraction_params=form_extraction_params,
+ )
+
+ # Add each ProcessDocumentRequest to the batch request
+ requests = []
+ requests.append(request)
+
+ batch_request = documentai.types.BatchProcessDocumentsRequest(
+ parent=parent, requests=requests
+ )
+
+ operation = client.batch_process_documents(batch_request)
+
+ # Wait for the operation to finish
+ operation.result(timeout)
+
+ # Results are written to GCS. Use a regex to find
+ # output files
+ match = re.match(r"gs://([^/]+)/(.+)", destination_uri)
+ output_bucket = match.group(1)
+ prefix = match.group(2)
+
+ storage_client = storage.client.Client()
+ bucket = storage_client.get_bucket(output_bucket)
+ blob_list = list(bucket.list_blobs(prefix=prefix))
+ print("Output files:")
+ for blob in blob_list:
+ print(blob.name)
+
+
+# [END documentai_batch_parse_form_beta]
diff --git a/samples/snippets/batch_parse_form_v1beta2_test.py b/samples/snippets/batch_parse_form_v1beta2_test.py
new file mode 100644
index 00000000..6abd19a2
--- /dev/null
+++ b/samples/snippets/batch_parse_form_v1beta2_test.py
@@ -0,0 +1,46 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+import uuid
+
+from google.cloud import storage
+
+import pytest
+
+from samples.snippets import batch_parse_form_v1beta2
+
+
+BUCKET = "document-ai-{}".format(uuid.uuid4())
+OUTPUT_PREFIX = "TEST_OUTPUT_{}".format(uuid.uuid4())
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+BATCH_OUTPUT_URI = "gs://{}/{}/".format(BUCKET, OUTPUT_PREFIX)
+
+
+@pytest.fixture(autouse=True)
+def setup_teardown():
+ """Create a temporary bucket to store annotation output."""
+ storage_client = storage.Client()
+ bucket = storage_client.create_bucket(BUCKET)
+
+ yield
+
+ bucket.delete(force=True)
+
+
+def test_batch_parse_form(capsys):
+ batch_parse_form_v1beta2.batch_parse_form(PROJECT_ID, INPUT_URI, BATCH_OUTPUT_URI, 120)
+ out, _ = capsys.readouterr()
+ assert "Output files" in out
diff --git a/samples/snippets/batch_parse_table_v1beta2.py b/samples/snippets/batch_parse_table_v1beta2.py
new file mode 100644
index 00000000..f62495b4
--- /dev/null
+++ b/samples/snippets/batch_parse_table_v1beta2.py
@@ -0,0 +1,108 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# [START documentai_batch_parse_table_beta]
+import re
+
+from google.cloud import documentai_v1beta2 as documentai
+from google.cloud import storage
+
+
+def batch_parse_table(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/form.pdf",
+ destination_uri="gs://your-bucket-id/path/to/save/results/",
+ timeout=90
+):
+ """Parse a form"""
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # where to write results
+ output_config = documentai.types.OutputConfig(
+ gcs_destination=documentai.types.GcsDestination(uri=destination_uri),
+ pages_per_shard=1, # Map one doc page to one output page
+ )
+
+ # Improve table parsing results by providing bounding boxes
+ # specifying where the box appears in the document (optional)
+ table_bound_hints = [
+ documentai.types.TableBoundHint(
+ page_number=1,
+ bounding_box=documentai.types.BoundingPoly(
+ # Define a polygon around tables to detect
+ # Each vertice coordinate must be a number between 0 and 1
+ normalized_vertices=[
+ # Top left
+ documentai.types.geometry.NormalizedVertex(x=0, y=0),
+ # Top right
+ documentai.types.geometry.NormalizedVertex(x=1, y=0),
+ # Bottom right
+ documentai.types.geometry.NormalizedVertex(x=1, y=1),
+ # Bottom left
+ documentai.types.geometry.NormalizedVertex(x=0, y=1),
+ ]
+ ),
+ )
+ ]
+
+ # Setting enabled=True enables form extraction
+ table_extraction_params = documentai.types.TableExtractionParams(
+ enabled=True, table_bound_hints=table_bound_hints
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ input_config=input_config,
+ output_config=output_config,
+ table_extraction_params=table_extraction_params,
+ )
+
+ requests = []
+ requests.append(request)
+
+ batch_request = documentai.types.BatchProcessDocumentsRequest(
+ parent=parent, requests=requests
+ )
+
+ operation = client.batch_process_documents(batch_request)
+
+ # Wait for the operation to finish
+ operation.result(timeout)
+
+ # Results are written to GCS. Use a regex to find
+ # output files
+ match = re.match(r"gs://([^/]+)/(.+)", destination_uri)
+ output_bucket = match.group(1)
+ prefix = match.group(2)
+
+ storage_client = storage.client.Client()
+ bucket = storage_client.get_bucket(output_bucket)
+ blob_list = list(bucket.list_blobs(prefix=prefix))
+ print("Output files:")
+ for blob in blob_list:
+ print(blob.name)
+
+
+# [END documentai_batch_parse_table_beta]
diff --git a/samples/snippets/batch_parse_table_v1beta2_test.py b/samples/snippets/batch_parse_table_v1beta2_test.py
new file mode 100644
index 00000000..aa890520
--- /dev/null
+++ b/samples/snippets/batch_parse_table_v1beta2_test.py
@@ -0,0 +1,46 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+import uuid
+
+from google.cloud import storage
+
+import pytest
+
+from samples.snippets import batch_parse_table_v1beta2
+
+
+BUCKET = "document-ai-{}".format(uuid.uuid4())
+OUTPUT_PREFIX = "TEST_OUTPUT_{}".format(uuid.uuid4())
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+BATCH_OUTPUT_URI = "gs://{}/{}/".format(BUCKET, OUTPUT_PREFIX)
+
+
+@pytest.fixture(autouse=True)
+def setup_teardown():
+ """Create a temporary bucket to store annotation output."""
+ storage_client = storage.Client()
+ bucket = storage_client.create_bucket(BUCKET)
+
+ yield
+
+ bucket.delete(force=True)
+
+
+def test_batch_parse_table(capsys):
+ batch_parse_table_v1beta2.batch_parse_table(PROJECT_ID, INPUT_URI, BATCH_OUTPUT_URI, 120)
+ out, _ = capsys.readouterr()
+ assert "Output files:" in out
diff --git a/samples/snippets/batch_process_documents_sample_bad_input_v1beta3_test.py b/samples/snippets/batch_process_documents_sample_bad_input_v1beta3_test.py
new file mode 100644
index 00000000..e0a7e468
--- /dev/null
+++ b/samples/snippets/batch_process_documents_sample_bad_input_v1beta3_test.py
@@ -0,0 +1,44 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+from uuid import uuid4
+
+from samples.snippets import batch_process_documents_sample_v1beta3
+
+location = "us"
+project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
+processor_id = "90484cfdedb024f6"
+gcs_input_uri = "gs://cloud-samples-data/documentai/invoice.pdf"
+# following bucket contains .csv file which will cause the sample to fail.
+gcs_output_full_uri_with_wrong_type = "gs://documentai-beta-samples"
+BUCKET_NAME = f"document-ai-python-{uuid4()}"
+
+
+def test_batch_process_documents_with_bad_input(capsys):
+ try:
+ batch_process_documents_sample_v1beta3.batch_process_documents(
+ project_id=project_id,
+ location=location,
+ processor_id=processor_id,
+ gcs_input_uri=gcs_input_uri,
+ gcs_output_uri=gcs_output_full_uri_with_wrong_type,
+ gcs_output_uri_prefix="test",
+ timeout=450,
+ )
+ out, _ = capsys.readouterr()
+ assert "Failed to process" in out
+ except Exception as e:
+ assert "Failed to process" in e.message
diff --git a/samples/snippets/batch_process_documents_sample_v1beta3.py b/samples/snippets/batch_process_documents_sample_v1beta3.py
new file mode 100644
index 00000000..b1ed3226
--- /dev/null
+++ b/samples/snippets/batch_process_documents_sample_v1beta3.py
@@ -0,0 +1,131 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# [START documentai_batch_process_document]
+import re
+
+from google.cloud import documentai_v1beta3 as documentai
+from google.cloud import storage
+
+# TODO(developer): Uncomment these variables before running the sample.
+# project_id= 'YOUR_PROJECT_ID'
+# location = 'YOUR_PROJECT_LOCATION' # Format is 'us' or 'eu'
+# processor_id = 'YOUR_PROCESSOR_ID' # Create processor in Cloud Console
+# gcs_input_uri = "YOUR_INPUT_URI"
+# gcs_output_uri = "YOUR_OUTPUT_BUCKET_URI"
+# gcs_output_uri_prefix = "YOUR_OUTPUT_URI_PREFIX"
+
+
+def batch_process_documents(
+ project_id,
+ location,
+ processor_id,
+ gcs_input_uri,
+ gcs_output_uri,
+ gcs_output_uri_prefix,
+ timeout: int = 300,
+):
+
+ # You must set the api_endpoint if you use a location other than 'us', e.g.:
+ opts = {}
+ if location == "eu":
+ opts = {"api_endpoint": "eu-documentai.googleapis.com"}
+
+ client = documentai.DocumentProcessorServiceClient(client_options=opts)
+
+ destination_uri = f"{gcs_output_uri}/{gcs_output_uri_prefix}/"
+
+ # 'mime_type' can be 'application/pdf', 'image/tiff',
+ # and 'image/gif', or 'application/json'
+ input_config = documentai.types.document_processor_service.BatchProcessRequest.BatchInputConfig(
+ gcs_source=gcs_input_uri, mime_type="application/pdf"
+ )
+
+ # Where to write results
+ output_config = documentai.types.document_processor_service.BatchProcessRequest.BatchOutputConfig(
+ gcs_destination=destination_uri
+ )
+
+ # Location can be 'us' or 'eu'
+ name = f"projects/{project_id}/locations/{location}/processors/{processor_id}"
+ request = documentai.types.document_processor_service.BatchProcessRequest(
+ name=name,
+ input_configs=[input_config],
+ output_config=output_config,
+ )
+
+ operation = client.batch_process_documents(request)
+
+ # Wait for the operation to finish
+ operation.result(timeout=timeout)
+
+ # Results are written to GCS. Use a regex to find
+ # output files
+ match = re.match(r"gs://([^/]+)/(.+)", destination_uri)
+ output_bucket = match.group(1)
+ prefix = match.group(2)
+
+ storage_client = storage.Client()
+ bucket = storage_client.get_bucket(output_bucket)
+ blob_list = list(bucket.list_blobs(prefix=prefix))
+ print("Output files:")
+
+ for i, blob in enumerate(blob_list):
+ # If JSON file, download the contents of this blob as a bytes object.
+ if ".json" in blob.name:
+ blob_as_bytes = blob.download_as_bytes()
+
+ document = documentai.types.Document.from_json(blob_as_bytes)
+ print(f"Fetched file {i + 1}")
+
+ # For a full list of Document object attributes, please reference this page:
+ # https://cloud.google.com/document-ai/docs/reference/rpc/google.cloud.documentai.v1beta3#document
+
+ # Read the text recognition output from the processor
+ for page in document.pages:
+ for form_field in page.form_fields:
+ field_name = get_text(form_field.field_name, document)
+ field_value = get_text(form_field.field_value, document)
+ print("Extracted key value pair:")
+ print(f"\t{field_name}, {field_value}")
+ for paragraph in document.pages:
+ paragraph_text = get_text(paragraph.layout, document)
+ print(f"Paragraph text:\n{paragraph_text}")
+ else:
+ print(f"Skipping non-supported file type {blob.name}")
+
+
+# Extract shards from the text field
+def get_text(doc_element: dict, document: dict):
+ """
+ Document AI identifies form fields by their offsets
+ in document text. This function converts offsets
+ to text snippets.
+ """
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in doc_element.text_anchor.text_segments:
+ start_index = (
+ int(segment.start_index)
+ if segment in doc_element.text_anchor.text_segments
+ else 0
+ )
+ end_index = int(segment.end_index)
+ response += document.text[start_index:end_index]
+ return response
+
+
+# [END documentai_batch_process_document]
diff --git a/samples/snippets/batch_process_documents_sample_v1beta3_test.py b/samples/snippets/batch_process_documents_sample_v1beta3_test.py
new file mode 100644
index 00000000..dcb63567
--- /dev/null
+++ b/samples/snippets/batch_process_documents_sample_v1beta3_test.py
@@ -0,0 +1,62 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+from uuid import uuid4
+
+from google.cloud import storage
+from google.cloud.exceptions import NotFound
+
+import pytest
+
+from samples.snippets import batch_process_documents_sample_v1beta3
+
+location = "us"
+project_id = os.environ["GOOGLE_CLOUD_PROJECT"]
+processor_id = "90484cfdedb024f6"
+gcs_input_uri = "gs://cloud-samples-data/documentai/invoice.pdf"
+gcs_output_uri_prefix = uuid4()
+BUCKET_NAME = f"document-ai-python-{uuid4()}"
+
+
+@pytest.fixture(scope="module")
+def test_bucket():
+ storage_client = storage.Client()
+ bucket = storage_client.create_bucket(BUCKET_NAME)
+ yield bucket.name
+
+ try:
+ blobs = list(bucket.list_blobs())
+ for blob in blobs:
+ blob.delete()
+ bucket.delete()
+ except NotFound:
+ print("Bucket already deleted.")
+
+
+def test_batch_process_documents(capsys, test_bucket):
+ batch_process_documents_sample_v1beta3.batch_process_documents(
+ project_id=project_id,
+ location=location,
+ processor_id=processor_id,
+ gcs_input_uri=gcs_input_uri,
+ gcs_output_uri=f"gs://{test_bucket}",
+ gcs_output_uri_prefix=gcs_output_uri_prefix,
+ )
+ out, _ = capsys.readouterr()
+
+ assert "Extracted" in out
+ assert "Paragraph" in out
+ assert "Invoice" in out
diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py
new file mode 100644
index 00000000..97bf7da8
--- /dev/null
+++ b/samples/snippets/noxfile.py
@@ -0,0 +1,247 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+
+import os
+from pathlib import Path
+import sys
+from typing import Callable, Dict, List, Optional
+
+import nox
+
+
+# WARNING - WARNING - WARNING - WARNING - WARNING
+# WARNING - WARNING - WARNING - WARNING - WARNING
+# DO NOT EDIT THIS FILE EVER!
+# WARNING - WARNING - WARNING - WARNING - WARNING
+# WARNING - WARNING - WARNING - WARNING - WARNING
+
+# Copy `noxfile_config.py` to your directory and modify it instead.
+
+
+# `TEST_CONFIG` dict is a configuration hook that allows users to
+# modify the test configurations. The values here should be in sync
+# with `noxfile_config.py`. Users will copy `noxfile_config.py` into
+# their directory and modify it.
+
+TEST_CONFIG = {
+ # You can opt out from the test for specific Python versions.
+ 'ignored_versions': ["2.7"],
+
+ # Old samples are opted out of enforcing Python type hints
+ # All new samples should feature them
+ 'enforce_type_hints': False,
+
+ # An envvar key for determining the project id to use. Change it
+ # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
+ # build specific Cloud project. You can also use your own string
+ # to use your own Cloud project.
+ 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT',
+ # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',
+
+ # A dictionary you want to inject into your test. Don't put any
+ # secrets here. These values will override predefined values.
+ 'envs': {},
+}
+
+
+try:
+ # Ensure we can import noxfile_config in the project's directory.
+ sys.path.append('.')
+ from noxfile_config import TEST_CONFIG_OVERRIDE
+except ImportError as e:
+ print("No user noxfile_config found: detail: {}".format(e))
+ TEST_CONFIG_OVERRIDE = {}
+
+# Update the TEST_CONFIG with the user supplied values.
+TEST_CONFIG.update(TEST_CONFIG_OVERRIDE)
+
+
+def get_pytest_env_vars() -> Dict[str, str]:
+ """Returns a dict for pytest invocation."""
+ ret = {}
+
+ # Override the GCLOUD_PROJECT and the alias.
+ env_key = TEST_CONFIG['gcloud_project_env']
+ # This should error out if not set.
+ ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key]
+
+ # Apply user supplied envs.
+ ret.update(TEST_CONFIG['envs'])
+ return ret
+
+
+# DO NOT EDIT - automatically generated.
+# All versions used to tested samples.
+ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"]
+
+# Any default versions that should be ignored.
+IGNORED_VERSIONS = TEST_CONFIG['ignored_versions']
+
+TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS])
+
+INSTALL_LIBRARY_FROM_SOURCE = bool(os.environ.get("INSTALL_LIBRARY_FROM_SOURCE", False))
+#
+# Style Checks
+#
+
+
+def _determine_local_import_names(start_dir: str) -> List[str]:
+ """Determines all import names that should be considered "local".
+
+ This is used when running the linter to insure that import order is
+ properly checked.
+ """
+ file_ext_pairs = [os.path.splitext(path) for path in os.listdir(start_dir)]
+ return [
+ basename
+ for basename, extension in file_ext_pairs
+ if extension == ".py"
+ or os.path.isdir(os.path.join(start_dir, basename))
+ and basename not in ("__pycache__")
+ ]
+
+
+# Linting with flake8.
+#
+# We ignore the following rules:
+# E203: whitespace before ‘:’
+# E266: too many leading ‘#’ for block comment
+# E501: line too long
+# I202: Additional newline in a section of imports
+#
+# We also need to specify the rules which are ignored by default:
+# ['E226', 'W504', 'E126', 'E123', 'W503', 'E24', 'E704', 'E121']
+FLAKE8_COMMON_ARGS = [
+ "--show-source",
+ "--builtin=gettext",
+ "--max-complexity=20",
+ "--import-order-style=google",
+ "--exclude=.nox,.cache,env,lib,generated_pb2,*_pb2.py,*_pb2_grpc.py",
+ "--ignore=E121,E123,E126,E203,E226,E24,E266,E501,E704,W503,W504,I202",
+ "--max-line-length=88",
+]
+
+
+@nox.session
+def lint(session: nox.sessions.Session) -> None:
+ if not TEST_CONFIG['enforce_type_hints']:
+ session.install("flake8", "flake8-import-order")
+ else:
+ session.install("flake8", "flake8-import-order", "flake8-annotations")
+
+ local_names = _determine_local_import_names(".")
+ args = FLAKE8_COMMON_ARGS + [
+ "--application-import-names",
+ ",".join(local_names),
+ "."
+ ]
+ session.run("flake8", *args)
+#
+# Black
+#
+
+
+@nox.session
+def blacken(session: nox.sessions.Session) -> None:
+ session.install("black")
+ python_files = [path for path in os.listdir(".") if path.endswith(".py")]
+
+ session.run("black", *python_files)
+
+#
+# Sample Tests
+#
+
+
+PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"]
+
+
+def _session_tests(session: nox.sessions.Session, post_install: Callable = None) -> None:
+ """Runs py.test for a particular project."""
+ if os.path.exists("requirements.txt"):
+ session.install("-r", "requirements.txt")
+
+ if os.path.exists("requirements-test.txt"):
+ session.install("-r", "requirements-test.txt")
+
+ if INSTALL_LIBRARY_FROM_SOURCE:
+ session.install("-e", _get_repo_root())
+
+ if post_install:
+ post_install(session)
+
+ session.run(
+ "pytest",
+ *(PYTEST_COMMON_ARGS + session.posargs),
+ # Pytest will return 5 when no tests are collected. This can happen
+ # on travis where slow and flaky tests are excluded.
+ # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html
+ success_codes=[0, 5],
+ env=get_pytest_env_vars()
+ )
+
+
+@nox.session(python=ALL_VERSIONS)
+def py(session: nox.sessions.Session) -> None:
+ """Runs py.test for a sample using the specified version of Python."""
+ if session.python in TESTED_VERSIONS:
+ _session_tests(session)
+ else:
+ session.skip("SKIPPED: {} tests are disabled for this sample.".format(
+ session.python
+ ))
+
+
+#
+# Readmegen
+#
+
+
+def _get_repo_root() -> Optional[str]:
+ """ Returns the root folder of the project. """
+ # Get root of this repository. Assume we don't have directories nested deeper than 10 items.
+ p = Path(os.getcwd())
+ for i in range(10):
+ if p is None:
+ break
+ if Path(p / ".git").exists():
+ return str(p)
+ # .git is not available in repos cloned via Cloud Build
+ # setup.py is always in the library's root, so use that instead
+ # https://github.com/googleapis/synthtool/issues/792
+ if Path(p / "setup.py").exists():
+ return str(p)
+ p = p.parent
+ raise Exception("Unable to detect repository root.")
+
+
+GENERATED_READMES = sorted([x for x in Path(".").rglob("*.rst.in")])
+
+
+@nox.session
+@nox.parametrize("path", GENERATED_READMES)
+def readmegen(session: nox.sessions.Session, path: str) -> None:
+ """(Re-)generates the readme for a sample."""
+ session.install("jinja2", "pyyaml")
+ dir_ = os.path.dirname(path)
+
+ if os.path.exists(os.path.join(dir_, "requirements.txt")):
+ session.install("-r", os.path.join(dir_, "requirements.txt"))
+
+ in_file = os.path.join(dir_, "README.rst.in")
+ session.run(
+ "python", _get_repo_root() + "/scripts/readme-gen/readme_gen.py", in_file
+ )
diff --git a/samples/snippets/parse_form_v1beta2.py b/samples/snippets/parse_form_v1beta2.py
new file mode 100644
index 00000000..27c99811
--- /dev/null
+++ b/samples/snippets/parse_form_v1beta2.py
@@ -0,0 +1,92 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the 'License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# [START documentai_parse_form_beta]
+from google.cloud import documentai_v1beta2 as documentai
+
+
+def parse_form(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/form.pdf",
+):
+ """Parse a form"""
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # Improve form parsing results by providing key-value pair hints.
+ # For each key hint, key is text that is likely to appear in the
+ # document as a form field name (i.e. "DOB").
+ # Value types are optional, but can be one or more of:
+ # ADDRESS, LOCATION, ORGANIZATION, PERSON, PHONE_NUMBER, ID,
+ # NUMBER, EMAIL, PRICE, TERMS, DATE, NAME
+ key_value_pair_hints = [
+ documentai.types.KeyValuePairHint(
+ key="Emergency Contact", value_types=["NAME"]
+ ),
+ documentai.types.KeyValuePairHint(key="Referred By"),
+ ]
+
+ # Setting enabled=True enables form extraction
+ form_extraction_params = documentai.types.FormExtractionParams(
+ enabled=True, key_value_pair_hints=key_value_pair_hints
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ parent=parent,
+ input_config=input_config,
+ form_extraction_params=form_extraction_params,
+ )
+
+ document = client.process_document(request=request)
+
+ def _get_text(el):
+ """Doc AI identifies form fields by their offsets
+ in document text. This function converts offsets
+ to text snippets.
+ """
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in el.text_anchor.text_segments:
+ start_index = segment.start_index
+ end_index = segment.end_index
+ response += document.text[start_index:end_index]
+ return response
+
+ for page in document.pages:
+ print("Page number: {}".format(page.page_number))
+ for form_field in page.form_fields:
+ print(
+ "Field Name: {}\tConfidence: {}".format(
+ _get_text(form_field.field_name), form_field.field_name.confidence
+ )
+ )
+ print(
+ "Field Value: {}\tConfidence: {}".format(
+ _get_text(form_field.field_value), form_field.field_value.confidence
+ )
+ )
+
+
+# [END documentai_parse_form_beta]
diff --git a/samples/snippets/parse_form_v1beta2_test.py b/samples/snippets/parse_form_v1beta2_test.py
new file mode 100644
index 00000000..6987612a
--- /dev/null
+++ b/samples/snippets/parse_form_v1beta2_test.py
@@ -0,0 +1,28 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+
+from samples.snippets import parse_form_v1beta2
+
+
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/form.pdf"
+
+
+def test_parse_form(capsys):
+ parse_form_v1beta2.parse_form(PROJECT_ID, INPUT_URI)
+ out, _ = capsys.readouterr()
+ assert "Field Name" in out
+ assert "Field Value" in out
diff --git a/samples/snippets/parse_table_v1beta2.py b/samples/snippets/parse_table_v1beta2.py
new file mode 100644
index 00000000..ac8f5d11
--- /dev/null
+++ b/samples/snippets/parse_table_v1beta2.py
@@ -0,0 +1,95 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# [START documentai_parse_table_beta]
+from google.cloud import documentai_v1beta2 as documentai
+
+
+def parse_table(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/invoice.pdf",
+):
+ """Parse a form"""
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # Improve table parsing results by providing bounding boxes
+ # specifying where the box appears in the document (optional)
+ table_bound_hints = [
+ documentai.types.TableBoundHint(
+ page_number=1,
+ bounding_box=documentai.types.BoundingPoly(
+ # Define a polygon around tables to detect
+ # Each vertice coordinate must be a number between 0 and 1
+ normalized_vertices=[
+ # Top left
+ documentai.types.geometry.NormalizedVertex(x=0, y=0),
+ # Top right
+ documentai.types.geometry.NormalizedVertex(x=1, y=0),
+ # Bottom right
+ documentai.types.geometry.NormalizedVertex(x=1, y=1),
+ # Bottom left
+ documentai.types.geometry.NormalizedVertex(x=0, y=1),
+ ]
+ ),
+ )
+ ]
+
+ # Setting enabled=True enables form extraction
+ table_extraction_params = documentai.types.TableExtractionParams(
+ enabled=True, table_bound_hints=table_bound_hints
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ parent=parent,
+ input_config=input_config,
+ table_extraction_params=table_extraction_params,
+ )
+
+ document = client.process_document(request=request)
+
+ def _get_text(el):
+ """Convert text offset indexes into text snippets."""
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in el.text_anchor.text_segments:
+ start_index = segment.start_index
+ end_index = segment.end_index
+ response += document.text[start_index:end_index]
+ return response
+
+ for page in document.pages:
+ print("Page number: {}".format(page.page_number))
+ for table_num, table in enumerate(page.tables):
+ print("Table {}: ".format(table_num))
+ for row_num, row in enumerate(table.header_rows):
+ cells = "\t".join([_get_text(cell.layout) for cell in row.cells])
+ print("Header Row {}: {}".format(row_num, cells))
+ for row_num, row in enumerate(table.body_rows):
+ cells = "\t".join([_get_text(cell.layout) for cell in row.cells])
+ print("Row {}: {}".format(row_num, cells))
+
+
+# [END documentai_parse_table_beta]
diff --git a/samples/snippets/parse_table_v1beta2_test.py b/samples/snippets/parse_table_v1beta2_test.py
new file mode 100644
index 00000000..4102c926
--- /dev/null
+++ b/samples/snippets/parse_table_v1beta2_test.py
@@ -0,0 +1,28 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+
+from samples.snippets import parse_table_v1beta2
+
+
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+
+
+def test_parse_table(capsys):
+ parse_table_v1beta2.parse_table(PROJECT_ID, INPUT_URI)
+ out, _ = capsys.readouterr()
+ assert "Table" in out
+ assert "Header Row" in out
diff --git a/samples/snippets/parse_with_model_v1beta2.py b/samples/snippets/parse_with_model_v1beta2.py
new file mode 100644
index 00000000..59265c4f
--- /dev/null
+++ b/samples/snippets/parse_with_model_v1beta2.py
@@ -0,0 +1,60 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# [START documentai_parse_with_model_beta]
+from google.cloud import documentai_v1beta2 as documentai
+
+
+def parse_with_model(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/invoice.pdf",
+ automl_model_name="YOUR_AUTOML_MODEL_NAME",
+):
+ """Process a single document with the Document AI API.
+
+ Args:
+ project_id: your Google Cloud project id
+ input_uri: the Cloud Storage URI of your input PDF
+ automl_model_name: the AutoML model name formatted as:
+ `projects/[PROJECT_ID]/locations/[LOCATION]/models/[MODEL_ID]
+ where LOCATION is a Compute Engine region, e.g. `us-central1`
+ """
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ automl_params = documentai.types.AutoMlParams(model=automl_model_name)
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ parent=parent, input_config=input_config, automl_params=automl_params
+ )
+
+ document = client.process_document(request=request)
+
+ for label in document.labels:
+ print("Label detected: {}".format(label.name))
+ print("Confidence: {}".format(label.confidence))
+
+
+# [END documentai_parse_with_model_beta]
diff --git a/samples/snippets/parse_with_model_v1beta2_test.py b/samples/snippets/parse_with_model_v1beta2_test.py
new file mode 100644
index 00000000..4b5d3ca5
--- /dev/null
+++ b/samples/snippets/parse_with_model_v1beta2_test.py
@@ -0,0 +1,36 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+
+from samples.snippets import parse_with_model_v1beta2
+
+
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+AUTOML_NL_MODEL_ID = "TCN3472481026502981088"
+
+if "AUTOML_NL_MODEL_ID" in os.environ:
+ AUTOML_NL_MODEL_ID = os.environ["AUTOML_NL_MODEL_ID"]
+
+MODEL_NAME = "projects/{}/locations/us-central1/models/{}".format(
+ PROJECT_ID, AUTOML_NL_MODEL_ID
+)
+
+
+def test_parse_with_model(capsys):
+ parse_with_model_v1beta2.parse_with_model(PROJECT_ID, INPUT_URI, MODEL_NAME)
+ out, _ = capsys.readouterr()
+ assert "Label detected" in out
+ assert "Confidence" in out
diff --git a/samples/snippets/process_document_sample_v1beta3.py b/samples/snippets/process_document_sample_v1beta3.py
new file mode 100644
index 00000000..ab69d073
--- /dev/null
+++ b/samples/snippets/process_document_sample_v1beta3.py
@@ -0,0 +1,92 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# [START documentai_process_document]
+
+# TODO(developer): Uncomment these variables before running the sample.
+# project_id= 'YOUR_PROJECT_ID'
+# location = 'YOUR_PROJECT_LOCATION' # Format is 'us' or 'eu'
+# processor_id = 'YOUR_PROCESSOR_ID' # Create processor in Cloud Console
+# file_path = '/path/to/local/pdf'
+
+
+def process_document_sample(
+ project_id: str, location: str, processor_id: str, file_path: str
+):
+ from google.cloud import documentai_v1beta3 as documentai
+
+ # You must set the api_endpoint if you use a location other than 'us', e.g.:
+ opts = {}
+ if location == "eu":
+ opts = {"api_endpoint": "eu-documentai.googleapis.com"}
+
+ client = documentai.DocumentProcessorServiceClient(client_options=opts)
+
+ # The full resource name of the processor, e.g.:
+ # projects/project-id/locations/location/processor/processor-id
+ # You must create new processors in the Cloud Console first
+ name = f"projects/{project_id}/locations/{location}/processors/{processor_id}"
+
+ with open(file_path, "rb") as image:
+ image_content = image.read()
+
+ # Read the file into memory
+ document = {"content": image_content, "mime_type": "application/pdf"}
+
+ # Configure the process request
+ request = {"name": name, "document": document}
+
+ # Recognizes text entities in the PDF document
+ result = client.process_document(request=request)
+
+ document = result.document
+
+ print("Document processing complete.")
+
+ # For a full list of Document object attributes, please reference this page: https://googleapis.dev/python/documentai/latest/_modules/google/cloud/documentai_v1beta3/types/document.html#Document
+
+ document_pages = document.pages
+
+ # Read the text recognition output from the processor
+ print("The document contains the following paragraphs:")
+ for page in document_pages:
+ paragraphs = page.paragraphs
+ for paragraph in paragraphs:
+ paragraph_text = get_text(paragraph.layout, document)
+ print(f"Paragraph text: {paragraph_text}")
+
+
+# Extract shards from the text field
+def get_text(doc_element: dict, document: dict):
+ """
+ Document AI identifies form fields by their offsets
+ in document text. This function converts offsets
+ to text snippets.
+ """
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in doc_element.text_anchor.text_segments:
+ start_index = (
+ int(segment.start_index)
+ if segment in doc_element.text_anchor.text_segments
+ else 0
+ )
+ end_index = int(segment.end_index)
+ response += document.text[start_index:end_index]
+ return response
+
+
+# [END documentai_process_document]
diff --git a/samples/snippets/process_document_sample_v1beta3_test.py b/samples/snippets/process_document_sample_v1beta3_test.py
new file mode 100644
index 00000000..58b11b22
--- /dev/null
+++ b/samples/snippets/process_document_sample_v1beta3_test.py
@@ -0,0 +1,37 @@
+# # Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+
+from samples.snippets import process_document_sample_v1beta3
+
+
+location = "us"
+project_id = os.environ["GOOGLE_CLOUD_PROJECT"]
+processor_id = "90484cfdedb024f6"
+file_path = "resources/invoice.pdf"
+
+
+def test_process_documents(capsys):
+ process_document_sample_v1beta3.process_document_sample(
+ project_id=project_id,
+ location=location,
+ processor_id=processor_id,
+ file_path=file_path,
+ )
+ out, _ = capsys.readouterr()
+
+ assert "Paragraph" in out
+ assert "Invoice" in out
diff --git a/samples/snippets/quickstart_sample_v1beta3.py b/samples/snippets/quickstart_sample_v1beta3.py
new file mode 100644
index 00000000..884b412c
--- /dev/null
+++ b/samples/snippets/quickstart_sample_v1beta3.py
@@ -0,0 +1,87 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from google.cloud import documentai_v1beta3 as documentai
+
+# [START documentai_quickstart]
+
+# TODO(developer): Uncomment these variables before running the sample.
+# project_id= 'YOUR_PROJECT_ID'
+# location = 'YOUR_PROJECT_LOCATION' # Format is 'us' or 'eu'
+# processor_id = 'YOUR_PROCESSOR_ID' # Create processor in Cloud Console
+# file_path = '/path/to/local/pdf'
+
+
+def quickstart(project_id: str, location: str, processor_id: str, file_path: str):
+
+ # You must set the api_endpoint if you use a location other than 'us', e.g.:
+ opts = {}
+ if location == "eu":
+ opts = {"api_endpoint": "eu-documentai.googleapis.com"}
+
+ client = documentai.DocumentProcessorServiceClient(client_options=opts)
+
+ # The full resource name of the processor, e.g.:
+ # projects/project-id/locations/location/processor/processor-id
+ # You must create new processors in the Cloud Console first
+ name = f"projects/{project_id}/locations/{location}/processors/{processor_id}"
+
+ # Read the file into memory
+ with open(file_path, "rb") as image:
+ image_content = image.read()
+
+ document = {"content": image_content, "mime_type": "application/pdf"}
+
+ # Configure the process request
+ request = {"name": name, "document": document}
+
+ result = client.process_document(request=request)
+ document = result.document
+
+ document_pages = document.pages
+
+ # For a full list of Document object attributes, please reference this page: https://googleapis.dev/python/documentai/latest/_modules/google/cloud/documentai_v1beta3/types/document.html#Document
+
+ # Read the text recognition output from the processor
+ print("The document contains the following paragraphs:")
+ for page in document_pages:
+ paragraphs = page.paragraphs
+ for paragraph in paragraphs:
+ print(paragraph)
+ paragraph_text = get_text(paragraph.layout, document)
+ print(f"Paragraph text: {paragraph_text}")
+
+
+def get_text(doc_element: dict, document: dict):
+ """
+ Document AI identifies form fields by their offsets
+ in document text. This function converts offsets
+ to text snippets.
+ """
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in doc_element.text_anchor.text_segments:
+ start_index = (
+ int(segment.start_index)
+ if segment in doc_element.text_anchor.text_segments
+ else 0
+ )
+ end_index = int(segment.end_index)
+ response += document.text[start_index:end_index]
+ return response
+
+
+# [END documentai_quickstart]
diff --git a/samples/snippets/quickstart_sample_v1beta3_test.py b/samples/snippets/quickstart_sample_v1beta3_test.py
new file mode 100644
index 00000000..4badc1f7
--- /dev/null
+++ b/samples/snippets/quickstart_sample_v1beta3_test.py
@@ -0,0 +1,36 @@
+# # Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+
+from samples.snippets import quickstart_sample_v1beta3
+
+location = "us"
+project_id = os.environ["GOOGLE_CLOUD_PROJECT"]
+processor_id = "90484cfdedb024f6"
+file_path = "resources/invoice.pdf"
+
+
+def test_quickstart(capsys):
+ quickstart_sample_v1beta3.quickstart(
+ project_id=project_id,
+ location=location,
+ processor_id=processor_id,
+ file_path=file_path,
+ )
+ out, _ = capsys.readouterr()
+
+ assert "Paragraph" in out
+ assert "Invoice" in out
diff --git a/samples/snippets/quickstart_v1beta2.py b/samples/snippets/quickstart_v1beta2.py
new file mode 100644
index 00000000..34f58820
--- /dev/null
+++ b/samples/snippets/quickstart_v1beta2.py
@@ -0,0 +1,65 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# [START documentai_quickstart_beta]
+from google.cloud import documentai_v1beta2 as documentai
+
+
+def main(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/invoice.pdf",
+):
+ """Process a single document with the Document AI API, including
+ text extraction and entity extraction."""
+
+ client = documentai.DocumentUnderstandingServiceClient()
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/us".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ parent=parent, input_config=input_config
+ )
+
+ document = client.process_document(request=request)
+
+ # All text extracted from the document
+ print("Document Text: {}".format(document.text))
+
+ def _get_text(el):
+ """Convert text offset indexes into text snippets."""
+ response = ""
+ # If a text segment spans several lines, it will
+ # be stored in different text segments.
+ for segment in el.text_anchor.text_segments:
+ start_index = segment.start_index
+ end_index = segment.end_index
+ response += document.text[start_index:end_index]
+ return response
+
+ for entity in document.entities:
+ print("Entity type: {}".format(entity.type_))
+ print("Text: {}".format(_get_text(entity)))
+ print("Mention text: {}\n".format(entity.mention_text))
+
+
+# [END documentai_quickstart_beta]
diff --git a/samples/snippets/quickstart_v1beta2_test.py b/samples/snippets/quickstart_v1beta2_test.py
new file mode 100644
index 00000000..1868788d
--- /dev/null
+++ b/samples/snippets/quickstart_v1beta2_test.py
@@ -0,0 +1,28 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+
+from samples.snippets import quickstart_v1beta2
+
+
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+
+
+def test_quickstart(capsys):
+ quickstart_v1beta2.main(PROJECT_ID, INPUT_URI)
+ out, _ = capsys.readouterr()
+ assert "Entity type" in out
+ assert "Mention text" in out
diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt
new file mode 100644
index 00000000..be53becf
--- /dev/null
+++ b/samples/snippets/requirements-test.txt
@@ -0,0 +1 @@
+pytest==6.1.1
diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt
new file mode 100644
index 00000000..ce6670de
--- /dev/null
+++ b/samples/snippets/requirements.txt
@@ -0,0 +1,2 @@
+google-cloud-documentai==0.3.0
+google-cloud-storage==1.36.2
diff --git a/samples/snippets/resources/invoice.pdf b/samples/snippets/resources/invoice.pdf
new file mode 100644
index 00000000..7722734a
Binary files /dev/null and b/samples/snippets/resources/invoice.pdf differ
diff --git a/samples/snippets/set_endpoint_v1beta2.py b/samples/snippets/set_endpoint_v1beta2.py
new file mode 100644
index 00000000..0fa9921b
--- /dev/null
+++ b/samples/snippets/set_endpoint_v1beta2.py
@@ -0,0 +1,48 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def set_endpoint(
+ project_id="YOUR_PROJECT_ID",
+ input_uri="gs://cloud-samples-data/documentai/invoice.pdf",
+):
+ """Process a single document with the Document AI API, including
+ text extraction and entity extraction."""
+
+ # [START documentai_set_endpoint_beta]
+ from google.cloud import documentai_v1beta2 as documentai
+
+ client = documentai.DocumentUnderstandingServiceClient(
+ client_options={"api_endpoint": "eu-documentai.googleapis.com"}
+ )
+ # [END documentai_set_endpoint_beta]
+
+ gcs_source = documentai.types.GcsSource(uri=input_uri)
+
+ # mime_type can be application/pdf, image/tiff,
+ # and image/gif, or application/json
+ input_config = documentai.types.InputConfig(
+ gcs_source=gcs_source, mime_type="application/pdf"
+ )
+
+ # Location can be 'us' or 'eu'
+ parent = "projects/{}/locations/eu".format(project_id)
+ request = documentai.types.ProcessDocumentRequest(
+ parent=parent, input_config=input_config
+ )
+
+ document = client.process_document(request=request)
+
+ # All text extracted from the document
+ print("Document Text: {}".format(document.text))
diff --git a/samples/snippets/set_endpoint_v1beta2_test.py b/samples/snippets/set_endpoint_v1beta2_test.py
new file mode 100644
index 00000000..be535a28
--- /dev/null
+++ b/samples/snippets/set_endpoint_v1beta2_test.py
@@ -0,0 +1,27 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific ladnguage governing permissions and
+# limitations under the License.
+
+import os
+
+from samples.snippets import set_endpoint_v1beta2
+
+
+PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"]
+INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"
+
+
+def test_set_endpoint(capsys):
+ set_endpoint_v1beta2.set_endpoint(PROJECT_ID, INPUT_URI)
+ out, _ = capsys.readouterr()
+ assert "Document Text" in out
diff --git a/scripts/fixup_documentai_v1beta2_keywords.py b/scripts/fixup_documentai_v1beta2_keywords.py
deleted file mode 100644
index 0cb9fcbf..00000000
--- a/scripts/fixup_documentai_v1beta2_keywords.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import argparse
-import os
-import libcst as cst
-import pathlib
-import sys
-from typing import (Any, Callable, Dict, List, Sequence, Tuple)
-
-
-def partition(
- predicate: Callable[[Any], bool],
- iterator: Sequence[Any]
-) -> Tuple[List[Any], List[Any]]:
- """A stable, out-of-place partition."""
- results = ([], [])
-
- for i in iterator:
- results[int(predicate(i))].append(i)
-
- # Returns trueList, falseList
- return results[1], results[0]
-
-
-class documentaiCallTransformer(cst.CSTTransformer):
- CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
- METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
- 'batch_process_documents': ('requests', 'parent', ),
- 'process_document': ('input_config', 'parent', 'output_config', 'document_type', 'table_extraction_params', 'form_extraction_params', 'entity_extraction_params', 'ocr_params', 'automl_params', ),
-
- }
-
- def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
- try:
- key = original.func.attr.value
- kword_params = self.METHOD_TO_PARAMS[key]
- except (AttributeError, KeyError):
- # Either not a method from the API or too convoluted to be sure.
- return updated
-
- # If the existing code is valid, keyword args come after positional args.
- # Therefore, all positional args must map to the first parameters.
- args, kwargs = partition(lambda a: not bool(a.keyword), updated.args)
- if any(k.keyword.value == "request" for k in kwargs):
- # We've already fixed this file, don't fix it again.
- return updated
-
- kwargs, ctrl_kwargs = partition(
- lambda a: not a.keyword.value in self.CTRL_PARAMS,
- kwargs
- )
-
- args, ctrl_args = args[:len(kword_params)], args[len(kword_params):]
- ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl))
- for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS))
-
- request_arg = cst.Arg(
- value=cst.Dict([
- cst.DictElement(
- cst.SimpleString("'{}'".format(name)),
- cst.Element(value=arg.value)
- )
- # Note: the args + kwargs looks silly, but keep in mind that
- # the control parameters had to be stripped out, and that
- # those could have been passed positionally or by keyword.
- for name, arg in zip(kword_params, args + kwargs)]),
- keyword=cst.Name("request")
- )
-
- return updated.with_changes(
- args=[request_arg] + ctrl_kwargs
- )
-
-
-def fix_files(
- in_dir: pathlib.Path,
- out_dir: pathlib.Path,
- *,
- transformer=documentaiCallTransformer(),
-):
- """Duplicate the input dir to the output dir, fixing file method calls.
-
- Preconditions:
- * in_dir is a real directory
- * out_dir is a real, empty directory
- """
- pyfile_gen = (
- pathlib.Path(os.path.join(root, f))
- for root, _, files in os.walk(in_dir)
- for f in files if os.path.splitext(f)[1] == ".py"
- )
-
- for fpath in pyfile_gen:
- with open(fpath, 'r') as f:
- src = f.read()
-
- # Parse the code and insert method call fixes.
- tree = cst.parse_module(src)
- updated = tree.visit(transformer)
-
- # Create the path and directory structure for the new file.
- updated_path = out_dir.joinpath(fpath.relative_to(in_dir))
- updated_path.parent.mkdir(parents=True, exist_ok=True)
-
- # Generate the updated source file at the corresponding path.
- with open(updated_path, 'w') as f:
- f.write(updated.code)
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description="""Fix up source that uses the documentai client library.
-
-The existing sources are NOT overwritten but are copied to output_dir with changes made.
-
-Note: This tool operates at a best-effort level at converting positional
- parameters in client method calls to keyword based parameters.
- Cases where it WILL FAIL include
- A) * or ** expansion in a method call.
- B) Calls via function or method alias (includes free function calls)
- C) Indirect or dispatched calls (e.g. the method is looked up dynamically)
-
- These all constitute false negatives. The tool will also detect false
- positives when an API method shares a name with another method.
-""")
- parser.add_argument(
- '-d',
- '--input-directory',
- required=True,
- dest='input_dir',
- help='the input directory to walk for python files to fix up',
- )
- parser.add_argument(
- '-o',
- '--output-directory',
- required=True,
- dest='output_dir',
- help='the directory to output files fixed via un-flattening',
- )
- args = parser.parse_args()
- input_dir = pathlib.Path(args.input_dir)
- output_dir = pathlib.Path(args.output_dir)
- if not input_dir.is_dir():
- print(
- f"input directory '{input_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if not output_dir.is_dir():
- print(
- f"output directory '{output_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if os.listdir(output_dir):
- print(
- f"output directory '{output_dir}' is not empty",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- fix_files(input_dir, output_dir)
diff --git a/scripts/fixup_documentai_v1beta3_keywords.py b/scripts/fixup_documentai_v1beta3_keywords.py
deleted file mode 100644
index 2b689522..00000000
--- a/scripts/fixup_documentai_v1beta3_keywords.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import argparse
-import os
-import libcst as cst
-import pathlib
-import sys
-from typing import (Any, Callable, Dict, List, Sequence, Tuple)
-
-
-def partition(
- predicate: Callable[[Any], bool],
- iterator: Sequence[Any]
-) -> Tuple[List[Any], List[Any]]:
- """A stable, out-of-place partition."""
- results = ([], [])
-
- for i in iterator:
- results[int(predicate(i))].append(i)
-
- # Returns trueList, falseList
- return results[1], results[0]
-
-
-class documentaiCallTransformer(cst.CSTTransformer):
- CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
- METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
- 'batch_process_documents': ('name', 'input_configs', 'output_config', ),
- 'process_document': ('name', 'document', 'skip_human_review', ),
- 'review_document': ('human_review_config', 'document', ),
-
- }
-
- def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
- try:
- key = original.func.attr.value
- kword_params = self.METHOD_TO_PARAMS[key]
- except (AttributeError, KeyError):
- # Either not a method from the API or too convoluted to be sure.
- return updated
-
- # If the existing code is valid, keyword args come after positional args.
- # Therefore, all positional args must map to the first parameters.
- args, kwargs = partition(lambda a: not bool(a.keyword), updated.args)
- if any(k.keyword.value == "request" for k in kwargs):
- # We've already fixed this file, don't fix it again.
- return updated
-
- kwargs, ctrl_kwargs = partition(
- lambda a: not a.keyword.value in self.CTRL_PARAMS,
- kwargs
- )
-
- args, ctrl_args = args[:len(kword_params)], args[len(kword_params):]
- ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl))
- for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS))
-
- request_arg = cst.Arg(
- value=cst.Dict([
- cst.DictElement(
- cst.SimpleString("'{}'".format(name)),
- cst.Element(value=arg.value)
- )
- # Note: the args + kwargs looks silly, but keep in mind that
- # the control parameters had to be stripped out, and that
- # those could have been passed positionally or by keyword.
- for name, arg in zip(kword_params, args + kwargs)]),
- keyword=cst.Name("request")
- )
-
- return updated.with_changes(
- args=[request_arg] + ctrl_kwargs
- )
-
-
-def fix_files(
- in_dir: pathlib.Path,
- out_dir: pathlib.Path,
- *,
- transformer=documentaiCallTransformer(),
-):
- """Duplicate the input dir to the output dir, fixing file method calls.
-
- Preconditions:
- * in_dir is a real directory
- * out_dir is a real, empty directory
- """
- pyfile_gen = (
- pathlib.Path(os.path.join(root, f))
- for root, _, files in os.walk(in_dir)
- for f in files if os.path.splitext(f)[1] == ".py"
- )
-
- for fpath in pyfile_gen:
- with open(fpath, 'r') as f:
- src = f.read()
-
- # Parse the code and insert method call fixes.
- tree = cst.parse_module(src)
- updated = tree.visit(transformer)
-
- # Create the path and directory structure for the new file.
- updated_path = out_dir.joinpath(fpath.relative_to(in_dir))
- updated_path.parent.mkdir(parents=True, exist_ok=True)
-
- # Generate the updated source file at the corresponding path.
- with open(updated_path, 'w') as f:
- f.write(updated.code)
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description="""Fix up source that uses the documentai client library.
-
-The existing sources are NOT overwritten but are copied to output_dir with changes made.
-
-Note: This tool operates at a best-effort level at converting positional
- parameters in client method calls to keyword based parameters.
- Cases where it WILL FAIL include
- A) * or ** expansion in a method call.
- B) Calls via function or method alias (includes free function calls)
- C) Indirect or dispatched calls (e.g. the method is looked up dynamically)
-
- These all constitute false negatives. The tool will also detect false
- positives when an API method shares a name with another method.
-""")
- parser.add_argument(
- '-d',
- '--input-directory',
- required=True,
- dest='input_dir',
- help='the input directory to walk for python files to fix up',
- )
- parser.add_argument(
- '-o',
- '--output-directory',
- required=True,
- dest='output_dir',
- help='the directory to output files fixed via un-flattening',
- )
- args = parser.parse_args()
- input_dir = pathlib.Path(args.input_dir)
- output_dir = pathlib.Path(args.output_dir)
- if not input_dir.is_dir():
- print(
- f"input directory '{input_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if not output_dir.is_dir():
- print(
- f"output directory '{output_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if os.listdir(output_dir):
- print(
- f"output directory '{output_dir}' is not empty",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- fix_files(input_dir, output_dir)
diff --git a/scripts/fixup_keywords.py b/scripts/fixup_keywords.py
deleted file mode 100644
index 18b62b1c..00000000
--- a/scripts/fixup_keywords.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2020 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import argparse
-import os
-import libcst as cst
-import pathlib
-import sys
-from typing import (Any, Callable, Dict, List, Sequence, Tuple)
-
-
-def partition(
- predicate: Callable[[Any], bool],
- iterator: Sequence[Any]
-) -> Tuple[List[Any], List[Any]]:
- """A stable, out-of-place partition."""
- results = ([], [])
-
- for i in iterator:
- results[int(predicate(i))].append(i)
-
- # Returns trueList, falseList
- return results[1], results[0]
-
-
-class documentaiCallTransformer(cst.CSTTransformer):
- CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
- METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
- 'batch_process_documents': ('requests', 'parent', ),
- 'process_document': ('input_config', 'parent', 'output_config', 'document_type', 'table_extraction_params', 'form_extraction_params', 'entity_extraction_params', 'ocr_params', 'automl_params', ),
- }
-
- def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
- try:
- key = original.func.attr.value
- kword_params = self.METHOD_TO_PARAMS[key]
- except (AttributeError, KeyError):
- # Either not a method from the API or too convoluted to be sure.
- return updated
-
- # If the existing code is valid, keyword args come after positional args.
- # Therefore, all positional args must map to the first parameters.
- args, kwargs = partition(lambda a: not bool(a.keyword), updated.args)
- if any(k.keyword.value == "request" for k in kwargs):
- # We've already fixed this file, don't fix it again.
- return updated
-
- kwargs, ctrl_kwargs = partition(
- lambda a: not a.keyword.value in self.CTRL_PARAMS,
- kwargs
- )
-
- args, ctrl_args = args[:len(kword_params)], args[len(kword_params):]
- ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl))
- for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS))
-
- request_arg = cst.Arg(
- value=cst.Dict([
- cst.DictElement(
- cst.SimpleString("'{}'".format(name)),
- cst.Element(value=arg.value)
- )
- # Note: the args + kwargs looks silly, but keep in mind that
- # the control parameters had to be stripped out, and that
- # those could have been passed positionally or by keyword.
- for name, arg in zip(kword_params, args + kwargs)]),
- keyword=cst.Name("request")
- )
-
- return updated.with_changes(
- args=[request_arg] + ctrl_kwargs
- )
-
-
-def fix_files(
- in_dir: pathlib.Path,
- out_dir: pathlib.Path,
- *,
- transformer=documentaiCallTransformer(),
-):
- """Duplicate the input dir to the output dir, fixing file method calls.
-
- Preconditions:
- * in_dir is a real directory
- * out_dir is a real, empty directory
- """
- pyfile_gen = (
- pathlib.Path(os.path.join(root, f))
- for root, _, files in os.walk(in_dir)
- for f in files if os.path.splitext(f)[1] == ".py"
- )
-
- for fpath in pyfile_gen:
- with open(fpath, 'r') as f:
- src = f.read()
-
- # Parse the code and insert method call fixes.
- tree = cst.parse_module(src)
- updated = tree.visit(transformer)
-
- # Create the path and directory structure for the new file.
- updated_path = out_dir.joinpath(fpath.relative_to(in_dir))
- updated_path.parent.mkdir(parents=True, exist_ok=True)
-
- # Generate the updated source file at the corresponding path.
- with open(updated_path, 'w') as f:
- f.write(updated.code)
-
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description="""Fix up source that uses the documentai client library.
-
-The existing sources are NOT overwritten but are copied to output_dir with changes made.
-
-Note: This tool operates at a best-effort level at converting positional
- parameters in client method calls to keyword based parameters.
- Cases where it WILL FAIL include
- A) * or ** expansion in a method call.
- B) Calls via function or method alias (includes free function calls)
- C) Indirect or dispatched calls (e.g. the method is looked up dynamically)
-
- These all constitute false negatives. The tool will also detect false
- positives when an API method shares a name with another method.
-""")
- parser.add_argument(
- '-d',
- '--input-directory',
- required=True,
- dest='input_dir',
- help='the input directory to walk for python files to fix up',
- )
- parser.add_argument(
- '-o',
- '--output-directory',
- required=True,
- dest='output_dir',
- help='the directory to output files fixed via un-flattening',
- )
- args = parser.parse_args()
- input_dir = pathlib.Path(args.input_dir)
- output_dir = pathlib.Path(args.output_dir)
- if not input_dir.is_dir():
- print(
- f"input directory '{input_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if not output_dir.is_dir():
- print(
- f"output directory '{output_dir}' does not exist or is not a directory",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- if os.listdir(output_dir):
- print(
- f"output directory '{output_dir}' is not empty",
- file=sys.stderr,
- )
- sys.exit(-1)
-
- fix_files(input_dir, output_dir)
diff --git a/setup.py b/setup.py
index 90005d59..4cba6749 100644
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@
import os
import setuptools # type: ignore
-version = "0.3.0"
+version = "0.4.0"
package_root = os.path.abspath(os.path.dirname(__file__))
@@ -41,12 +41,11 @@
platforms="Posix; MacOS X; Windows",
include_package_data=True,
install_requires=(
- "google-api-core[grpc] >= 1.22.0, < 2.0.0dev",
+ "google-api-core[grpc] >= 1.22.2, < 2.0.0dev",
"proto-plus >= 1.10.0",
),
python_requires=">=3.6",
setup_requires=["libcst >= 0.2.5"],
- scripts=["scripts/fixup_keywords.py"],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
diff --git a/synth.metadata b/synth.metadata
index 3a7962ec..1d63eab3 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,21 +4,29 @@
"git": {
"name": ".",
"remote": "git@github.com:googleapis/python-documentai",
- "sha": "ec70a8cec0f1fbd0f8ec18189139e632ec28b025"
+ "sha": "9fd02a6b9ba34a6762a762f12de9948daf1ea9bb"
+ }
+ },
+ {
+ "git": {
+ "name": "googleapis",
+ "remote": "https://github.com/googleapis/googleapis.git",
+ "sha": "551ddbb55b96147012c00b66250dd5907556807c",
+ "internalRef": "364734171"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "e6168630be3e31eede633ba2c6f1cd64248dec1c"
+ "sha": "7a3df8832c7c64c482874c5dbebfd0a732b4938b"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "e6168630be3e31eede633ba2c6f1cd64248dec1c"
+ "sha": "7a3df8832c7c64c482874c5dbebfd0a732b4938b"
}
}
],
@@ -40,6 +48,15 @@
"language": "python",
"generator": "bazel"
}
+ },
+ {
+ "client": {
+ "source": "googleapis",
+ "apiName": "documentai",
+ "apiVersion": "v1",
+ "language": "python",
+ "generator": "bazel"
+ }
}
]
}
\ No newline at end of file
diff --git a/synth.py b/synth.py
index 3ae8445c..34c054d6 100644
--- a/synth.py
+++ b/synth.py
@@ -25,12 +25,11 @@
gapic = gcp.GAPICBazel()
common = gcp.CommonTemplates()
+# add the highest stable version to the end
+versions = ["v1beta2", "v1beta3", "v1"]
# ----------------------------------------------------------------------------
# Generate document AI GAPIC layer
# ----------------------------------------------------------------------------
-
-versions = ["v1beta2", "v1beta3"]
-
for version in versions:
library = gapic.py_library(
service="documentai",
@@ -38,22 +37,27 @@
bazel_target=f"//google/cloud/documentai/{version}:documentai-{version}-py",
)
- excludes = ["README.rst", "nox.py", "docs/index.rst", "setup.py"]
+ excludes = [
+ "README.rst",
+ "nox.py",
+ "docs/index.rst",
+ "setup.py",
+ "scripts/fixup_documentai_v*", # this library was always generated with the microgenerator
+ ]
s.move(library, excludes=excludes)
# ----------------------------------------------------------------------------
# Add templated files
# ----------------------------------------------------------------------------
templated_files = common.py_library(
- cov_level=100,
- microgenerator=True,
- samples=False, # set to true if there are samples
+ cov_level=99, microgenerator=True, samples=False, # set to true if there are samples
)
+
s.move(
templated_files,
excludes=[".coveragerc"], # microgenerator has a good .coveragerc file
-)
+)
-python.py_samples()
+python.py_samples(skip_readmes=True)
s.shell.run(["nox", "-s", "blacken"], hide_output=False)
diff --git a/testing/constraints-3.10.txt b/testing/constraints-3.10.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/testing/constraints-3.11.txt b/testing/constraints-3.11.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt
new file mode 100644
index 00000000..69e1c139
--- /dev/null
+++ b/testing/constraints-3.6.txt
@@ -0,0 +1,9 @@
+# This constraints file is used to check that lower bounds
+# are correct in setup.py
+# List *all* library dependencies and extras in this file.
+# Pin the version to the lower bound.
+#
+# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
+# Then this file should have foo==1.14.0
+google-api-core==1.22.2
+proto-plus==1.10.0
diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/testing/constraints-3.8.txt b/testing/constraints-3.8.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/testing/constraints-3.9.txt b/testing/constraints-3.9.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/gapic/documentai_v1/__init__.py b/tests/unit/gapic/documentai_v1/__init__.py
new file mode 100644
index 00000000..42ffdf2b
--- /dev/null
+++ b/tests/unit/gapic/documentai_v1/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git a/tests/unit/gapic/documentai_v1/test_document_processor_service.py b/tests/unit/gapic/documentai_v1/test_document_processor_service.py
new file mode 100644
index 00000000..b48bdc60
--- /dev/null
+++ b/tests/unit/gapic/documentai_v1/test_document_processor_service.py
@@ -0,0 +1,1723 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import mock
+
+import grpc
+from grpc.experimental import aio
+import math
+import pytest
+from proto.marshal.rules.dates import DurationRule, TimestampRule
+
+from google import auth
+from google.api_core import client_options
+from google.api_core import exceptions
+from google.api_core import future
+from google.api_core import gapic_v1
+from google.api_core import grpc_helpers
+from google.api_core import grpc_helpers_async
+from google.api_core import operation_async # type: ignore
+from google.api_core import operations_v1
+from google.auth import credentials
+from google.auth.exceptions import MutualTLSChannelError
+from google.cloud.documentai_v1.services.document_processor_service import (
+ DocumentProcessorServiceAsyncClient,
+)
+from google.cloud.documentai_v1.services.document_processor_service import (
+ DocumentProcessorServiceClient,
+)
+from google.cloud.documentai_v1.services.document_processor_service import transports
+from google.cloud.documentai_v1.types import document
+from google.cloud.documentai_v1.types import document_io
+from google.cloud.documentai_v1.types import document_processor_service
+from google.cloud.documentai_v1.types import geometry
+from google.longrunning import operations_pb2
+from google.oauth2 import service_account
+from google.protobuf import any_pb2 as gp_any # type: ignore
+from google.protobuf import duration_pb2 as duration # type: ignore
+from google.protobuf import timestamp_pb2 as timestamp # type: ignore
+from google.protobuf import wrappers_pb2 as wrappers # type: ignore
+from google.rpc import status_pb2 as status # type: ignore
+from google.type import color_pb2 as color # type: ignore
+from google.type import date_pb2 as date # type: ignore
+from google.type import datetime_pb2 as datetime # type: ignore
+from google.type import money_pb2 as money # type: ignore
+from google.type import postal_address_pb2 as postal_address # type: ignore
+
+
+def client_cert_source_callback():
+ return b"cert bytes", b"key bytes"
+
+
+# If default endpoint is localhost, then default mtls endpoint will be the same.
+# This method modifies the default endpoint so the client can produce a different
+# mtls endpoint for endpoint testing purposes.
+def modify_default_endpoint(client):
+ return (
+ "foo.googleapis.com"
+ if ("localhost" in client.DEFAULT_ENDPOINT)
+ else client.DEFAULT_ENDPOINT
+ )
+
+
+def test__get_default_mtls_endpoint():
+ api_endpoint = "example.googleapis.com"
+ api_mtls_endpoint = "example.mtls.googleapis.com"
+ sandbox_endpoint = "example.sandbox.googleapis.com"
+ sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com"
+ non_googleapi = "api.example.com"
+
+ assert DocumentProcessorServiceClient._get_default_mtls_endpoint(None) is None
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(api_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(api_mtls_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(sandbox_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(non_googleapi)
+ == non_googleapi
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class",
+ [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient,],
+)
+def test_document_processor_service_client_from_service_account_info(client_class):
+ creds = credentials.AnonymousCredentials()
+ with mock.patch.object(
+ service_account.Credentials, "from_service_account_info"
+ ) as factory:
+ factory.return_value = creds
+ info = {"valid": True}
+ client = client_class.from_service_account_info(info)
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
+
+ assert client.transport._host == "us-documentai.googleapis.com:443"
+
+
+@pytest.mark.parametrize(
+ "client_class",
+ [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient,],
+)
+def test_document_processor_service_client_from_service_account_file(client_class):
+ creds = credentials.AnonymousCredentials()
+ with mock.patch.object(
+ service_account.Credentials, "from_service_account_file"
+ ) as factory:
+ factory.return_value = creds
+ client = client_class.from_service_account_file("dummy/file/path.json")
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
+
+ client = client_class.from_service_account_json("dummy/file/path.json")
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
+
+ assert client.transport._host == "us-documentai.googleapis.com:443"
+
+
+def test_document_processor_service_client_get_transport_class():
+ transport = DocumentProcessorServiceClient.get_transport_class()
+ available_transports = [
+ transports.DocumentProcessorServiceGrpcTransport,
+ ]
+ assert transport in available_transports
+
+ transport = DocumentProcessorServiceClient.get_transport_class("grpc")
+ assert transport == transports.DocumentProcessorServiceGrpcTransport
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+@mock.patch.object(
+ DocumentProcessorServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceClient),
+)
+@mock.patch.object(
+ DocumentProcessorServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceAsyncClient),
+)
+def test_document_processor_service_client_client_options(
+ client_class, transport_class, transport_name
+):
+ # Check that if channel is provided we won't create a new one.
+ with mock.patch.object(
+ DocumentProcessorServiceClient, "get_transport_class"
+ ) as gtc:
+ transport = transport_class(credentials=credentials.AnonymousCredentials())
+ client = client_class(transport=transport)
+ gtc.assert_not_called()
+
+ # Check that if channel is provided via str we will create a new one.
+ with mock.patch.object(
+ DocumentProcessorServiceClient, "get_transport_class"
+ ) as gtc:
+ client = client_class(transport=transport_name)
+ gtc.assert_called()
+
+ # Check the case api_endpoint is provided.
+ options = client_options.ClientOptions(api_endpoint="squid.clam.whelk")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host="squid.clam.whelk",
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
+ # "never".
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
+ # "always".
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_MTLS_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has
+ # unsupported value.
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}):
+ with pytest.raises(MutualTLSChannelError):
+ client = client_class()
+
+ # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"}
+ ):
+ with pytest.raises(ValueError):
+ client = client_class()
+
+ # Check the case quota_project_id is provided
+ options = client_options.ClientOptions(quota_project_id="octopus")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id="octopus",
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name,use_client_cert_env",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ "true",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "true",
+ ),
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ "false",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "false",
+ ),
+ ],
+)
+@mock.patch.object(
+ DocumentProcessorServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceClient),
+)
+@mock.patch.object(
+ DocumentProcessorServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceAsyncClient),
+)
+@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
+def test_document_processor_service_client_mtls_env_auto(
+ client_class, transport_class, transport_name, use_client_cert_env
+):
+ # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default
+ # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists.
+
+ # Check the case client_cert_source is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ options = client_options.ClientOptions(
+ client_cert_source=client_cert_source_callback
+ )
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+
+ if use_client_cert_env == "false":
+ expected_client_cert_source = None
+ expected_host = client.DEFAULT_ENDPOINT
+ else:
+ expected_client_cert_source = client_cert_source_callback
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
+
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=expected_host,
+ scopes=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case ADC client cert is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=True,
+ ):
+ with mock.patch(
+ "google.auth.transport.mtls.default_client_cert_source",
+ return_value=client_cert_source_callback,
+ ):
+ if use_client_cert_env == "false":
+ expected_host = client.DEFAULT_ENDPOINT
+ expected_client_cert_source = None
+ else:
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
+ expected_client_cert_source = client_cert_source_callback
+
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=expected_host,
+ scopes=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case client_cert_source and ADC client cert are not provided.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=False,
+ ):
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_processor_service_client_client_options_scopes(
+ client_class, transport_class, transport_name
+):
+ # Check the case scopes are provided.
+ options = client_options.ClientOptions(scopes=["1", "2"],)
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=["1", "2"],
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_processor_service_client_client_options_credentials_file(
+ client_class, transport_class, transport_name
+):
+ # Check the case credentials file is provided.
+ options = client_options.ClientOptions(credentials_file="credentials.json")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file="credentials.json",
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+def test_document_processor_service_client_client_options_from_dict():
+ with mock.patch(
+ "google.cloud.documentai_v1.services.document_processor_service.transports.DocumentProcessorServiceGrpcTransport.__init__"
+ ) as grpc_transport:
+ grpc_transport.return_value = None
+ client = DocumentProcessorServiceClient(
+ client_options={"api_endpoint": "squid.clam.whelk"}
+ )
+ grpc_transport.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host="squid.clam.whelk",
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+def test_process_document(
+ transport: str = "grpc", request_type=document_processor_service.ProcessRequest
+):
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse()
+
+ response = client.process_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ProcessRequest()
+
+ # Establish that the response is the type that we expect.
+
+ assert isinstance(response, document_processor_service.ProcessResponse)
+
+
+def test_process_document_from_dict():
+ test_process_document(request_type=dict)
+
+
+def test_process_document_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ client.process_document()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ProcessRequest()
+
+
+@pytest.mark.asyncio
+async def test_process_document_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.ProcessRequest,
+):
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse()
+ )
+
+ response = await client.process_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ProcessRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, document_processor_service.ProcessResponse)
+
+
+@pytest.mark.asyncio
+async def test_process_document_async_from_dict():
+ await test_process_document_async(request_type=dict)
+
+
+def test_process_document_field_headers():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.ProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ call.return_value = document_processor_service.ProcessResponse()
+
+ client.process_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_process_document_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.ProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse()
+ )
+
+ await client.process_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+def test_process_document_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.process_document(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+def test_process_document_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.process_document(
+ document_processor_service.ProcessRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_process_document_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse()
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.process_document(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+@pytest.mark.asyncio
+async def test_process_document_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.process_document(
+ document_processor_service.ProcessRequest(), name="name_value",
+ )
+
+
+def test_batch_process_documents(
+ transport: str = "grpc", request_type=document_processor_service.BatchProcessRequest
+):
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/spam")
+
+ response = client.batch_process_documents(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.BatchProcessRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_batch_process_documents_from_dict():
+ test_batch_process_documents(request_type=dict)
+
+
+def test_batch_process_documents_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ client.batch_process_documents()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.BatchProcessRequest()
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.BatchProcessRequest,
+):
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+
+ response = await client.batch_process_documents(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.BatchProcessRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_async_from_dict():
+ await test_batch_process_documents_async(request_type=dict)
+
+
+def test_batch_process_documents_field_headers():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.BatchProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ client.batch_process_documents(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.BatchProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/op")
+ )
+
+ await client.batch_process_documents(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+def test_batch_process_documents_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.batch_process_documents(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+def test_batch_process_documents_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.batch_process_documents(
+ document_processor_service.BatchProcessRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.batch_process_documents(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.batch_process_documents(
+ document_processor_service.BatchProcessRequest(), name="name_value",
+ )
+
+
+def test_review_document(
+ transport: str = "grpc",
+ request_type=document_processor_service.ReviewDocumentRequest,
+):
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/spam")
+
+ response = client.review_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_review_document_from_dict():
+ test_review_document(request_type=dict)
+
+
+def test_review_document_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ client.review_document()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
+
+
+@pytest.mark.asyncio
+async def test_review_document_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.ReviewDocumentRequest,
+):
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+
+ response = await client.review_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+@pytest.mark.asyncio
+async def test_review_document_async_from_dict():
+ await test_review_document_async(request_type=dict)
+
+
+def test_review_document_field_headers():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.ReviewDocumentRequest()
+ request.human_review_config = "human_review_config/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ client.review_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert (
+ "x-goog-request-params",
+ "human_review_config=human_review_config/value",
+ ) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_review_document_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = document_processor_service.ReviewDocumentRequest()
+ request.human_review_config = "human_review_config/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/op")
+ )
+
+ await client.review_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert (
+ "x-goog-request-params",
+ "human_review_config=human_review_config/value",
+ ) in kw["metadata"]
+
+
+def test_review_document_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.review_document(human_review_config="human_review_config_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].human_review_config == "human_review_config_value"
+
+
+def test_review_document_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.review_document(
+ document_processor_service.ReviewDocumentRequest(),
+ human_review_config="human_review_config_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_review_document_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.review_document(
+ human_review_config="human_review_config_value",
+ )
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].human_review_config == "human_review_config_value"
+
+
+@pytest.mark.asyncio
+async def test_review_document_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.review_document(
+ document_processor_service.ReviewDocumentRequest(),
+ human_review_config="human_review_config_value",
+ )
+
+
+def test_credentials_transport_error():
+ # It is an error to provide credentials and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # It is an error to provide a credentials file and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ client_options={"credentials_file": "credentials.json"},
+ transport=transport,
+ )
+
+ # It is an error to provide scopes and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ client_options={"scopes": ["1", "2"]}, transport=transport,
+ )
+
+
+def test_transport_instance():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ client = DocumentProcessorServiceClient(transport=transport)
+ assert client.transport is transport
+
+
+def test_transport_get_channel():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+ transport = transports.DocumentProcessorServiceGrpcAsyncIOTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_transport_adc(transport_class):
+ # Test default credentials are used if not provided.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport_class()
+ adc.assert_called_once()
+
+
+def test_transport_grpc_default():
+ # A client should use the gRPC transport by default.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ assert isinstance(
+ client.transport, transports.DocumentProcessorServiceGrpcTransport,
+ )
+
+
+def test_document_processor_service_base_transport_error():
+ # Passing both a credentials object and credentials_file should raise an error
+ with pytest.raises(exceptions.DuplicateCredentialArgs):
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ credentials_file="credentials.json",
+ )
+
+
+def test_document_processor_service_base_transport():
+ # Instantiate the base transport.
+ with mock.patch(
+ "google.cloud.documentai_v1.services.document_processor_service.transports.DocumentProcessorServiceTransport.__init__"
+ ) as Transport:
+ Transport.return_value = None
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Every method on the transport should just blindly
+ # raise NotImplementedError.
+ methods = (
+ "process_document",
+ "batch_process_documents",
+ "review_document",
+ )
+ for method in methods:
+ with pytest.raises(NotImplementedError):
+ getattr(transport, method)(request=object())
+
+ # Additionally, the LRO client (a property) should
+ # also raise NotImplementedError
+ with pytest.raises(NotImplementedError):
+ transport.operations_client
+
+
+def test_document_processor_service_base_transport_with_credentials_file():
+ # Instantiate the base transport with a credentials file
+ with mock.patch.object(
+ auth, "load_credentials_from_file"
+ ) as load_creds, mock.patch(
+ "google.cloud.documentai_v1.services.document_processor_service.transports.DocumentProcessorServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ load_creds.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials_file="credentials.json", quota_project_id="octopus",
+ )
+ load_creds.assert_called_once_with(
+ "credentials.json",
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id="octopus",
+ )
+
+
+def test_document_processor_service_base_transport_with_adc():
+ # Test the default credentials are used if credentials and credentials_file are None.
+ with mock.patch.object(auth, "default") as adc, mock.patch(
+ "google.cloud.documentai_v1.services.document_processor_service.transports.DocumentProcessorServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentProcessorServiceTransport()
+ adc.assert_called_once()
+
+
+def test_document_processor_service_auth_adc():
+ # If no credentials are provided, we should use ADC credentials.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ DocumentProcessorServiceClient()
+ adc.assert_called_once_with(
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id=None,
+ )
+
+
+def test_document_processor_service_transport_auth_adc():
+ # If credentials and host are not provided, the transport class should use
+ # ADC credentials.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transports.DocumentProcessorServiceGrpcTransport(
+ host="squid.clam.whelk", quota_project_id="octopus"
+ )
+ adc.assert_called_once_with(
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id="octopus",
+ )
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_service_grpc_transport_client_cert_source_for_mtls(
+ transport_class,
+):
+ cred = credentials.AnonymousCredentials()
+
+ # Check ssl_channel_credentials is used if provided.
+ with mock.patch.object(transport_class, "create_channel") as mock_create_channel:
+ mock_ssl_channel_creds = mock.Mock()
+ transport_class(
+ host="squid.clam.whelk",
+ credentials=cred,
+ ssl_channel_credentials=mock_ssl_channel_creds,
+ )
+ mock_create_channel.assert_called_once_with(
+ "squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ ssl_credentials=mock_ssl_channel_creds,
+ quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
+ )
+
+ # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls
+ # is used.
+ with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()):
+ with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred:
+ transport_class(
+ credentials=cred,
+ client_cert_source_for_mtls=client_cert_source_callback,
+ )
+ expected_cert, expected_key = client_cert_source_callback()
+ mock_ssl_cred.assert_called_once_with(
+ certificate_chain=expected_cert, private_key=expected_key
+ )
+
+
+def test_document_processor_service_host_no_port():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="us-documentai.googleapis.com"
+ ),
+ )
+ assert client.transport._host == "us-documentai.googleapis.com:443"
+
+
+def test_document_processor_service_host_with_port():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="us-documentai.googleapis.com:8000"
+ ),
+ )
+ assert client.transport._host == "us-documentai.googleapis.com:8000"
+
+
+def test_document_processor_service_grpc_transport_channel():
+ channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials())
+
+ # Check that channel is used if provided.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+
+
+def test_document_processor_service_grpc_asyncio_transport_channel():
+ channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials())
+
+ # Check that channel is used if provided.
+ transport = transports.DocumentProcessorServiceGrpcAsyncIOTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+
+
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_service_transport_channel_mtls_with_client_cert_source(
+ transport_class,
+):
+ with mock.patch(
+ "grpc.ssl_channel_credentials", autospec=True
+ ) as grpc_ssl_channel_cred:
+ with mock.patch.object(
+ transport_class, "create_channel"
+ ) as grpc_create_channel:
+ mock_ssl_cred = mock.Mock()
+ grpc_ssl_channel_cred.return_value = mock_ssl_cred
+
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+
+ cred = credentials.AnonymousCredentials()
+ with pytest.warns(DeprecationWarning):
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (cred, None)
+ transport = transport_class(
+ host="squid.clam.whelk",
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=client_cert_source_callback,
+ )
+ adc.assert_called_once()
+
+ grpc_ssl_channel_cred.assert_called_once_with(
+ certificate_chain=b"cert bytes", private_key=b"key bytes"
+ )
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == mock_ssl_cred
+
+
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_service_transport_channel_mtls_with_adc(transport_class):
+ mock_ssl_cred = mock.Mock()
+ with mock.patch.multiple(
+ "google.auth.transport.grpc.SslCredentials",
+ __init__=mock.Mock(return_value=None),
+ ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
+ ):
+ with mock.patch.object(
+ transport_class, "create_channel"
+ ) as grpc_create_channel:
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+ mock_cred = mock.Mock()
+
+ with pytest.warns(DeprecationWarning):
+ transport = transport_class(
+ host="squid.clam.whelk",
+ credentials=mock_cred,
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=None,
+ )
+
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=mock_cred,
+ credentials_file=None,
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+
+
+def test_document_processor_service_grpc_lro_client():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+ transport = client.transport
+
+ # Ensure that we have a api-core operations client.
+ assert isinstance(transport.operations_client, operations_v1.OperationsClient,)
+
+ # Ensure that subsequent calls to the property send the exact same object.
+ assert transport.operations_client is transport.operations_client
+
+
+def test_document_processor_service_grpc_lro_async_client():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio",
+ )
+ transport = client.transport
+
+ # Ensure that we have a api-core operations client.
+ assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,)
+
+ # Ensure that subsequent calls to the property send the exact same object.
+ assert transport.operations_client is transport.operations_client
+
+
+def test_human_review_config_path():
+ project = "squid"
+ location = "clam"
+ processor = "whelk"
+
+ expected = "projects/{project}/locations/{location}/processors/{processor}/humanReviewConfig".format(
+ project=project, location=location, processor=processor,
+ )
+ actual = DocumentProcessorServiceClient.human_review_config_path(
+ project, location, processor
+ )
+ assert expected == actual
+
+
+def test_parse_human_review_config_path():
+ expected = {
+ "project": "octopus",
+ "location": "oyster",
+ "processor": "nudibranch",
+ }
+ path = DocumentProcessorServiceClient.human_review_config_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_human_review_config_path(path)
+ assert expected == actual
+
+
+def test_processor_path():
+ project = "cuttlefish"
+ location = "mussel"
+ processor = "winkle"
+
+ expected = "projects/{project}/locations/{location}/processors/{processor}".format(
+ project=project, location=location, processor=processor,
+ )
+ actual = DocumentProcessorServiceClient.processor_path(project, location, processor)
+ assert expected == actual
+
+
+def test_parse_processor_path():
+ expected = {
+ "project": "nautilus",
+ "location": "scallop",
+ "processor": "abalone",
+ }
+ path = DocumentProcessorServiceClient.processor_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_processor_path(path)
+ assert expected == actual
+
+
+def test_common_billing_account_path():
+ billing_account = "squid"
+
+ expected = "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+ actual = DocumentProcessorServiceClient.common_billing_account_path(billing_account)
+ assert expected == actual
+
+
+def test_parse_common_billing_account_path():
+ expected = {
+ "billing_account": "clam",
+ }
+ path = DocumentProcessorServiceClient.common_billing_account_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_billing_account_path(path)
+ assert expected == actual
+
+
+def test_common_folder_path():
+ folder = "whelk"
+
+ expected = "folders/{folder}".format(folder=folder,)
+ actual = DocumentProcessorServiceClient.common_folder_path(folder)
+ assert expected == actual
+
+
+def test_parse_common_folder_path():
+ expected = {
+ "folder": "octopus",
+ }
+ path = DocumentProcessorServiceClient.common_folder_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_folder_path(path)
+ assert expected == actual
+
+
+def test_common_organization_path():
+ organization = "oyster"
+
+ expected = "organizations/{organization}".format(organization=organization,)
+ actual = DocumentProcessorServiceClient.common_organization_path(organization)
+ assert expected == actual
+
+
+def test_parse_common_organization_path():
+ expected = {
+ "organization": "nudibranch",
+ }
+ path = DocumentProcessorServiceClient.common_organization_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_organization_path(path)
+ assert expected == actual
+
+
+def test_common_project_path():
+ project = "cuttlefish"
+
+ expected = "projects/{project}".format(project=project,)
+ actual = DocumentProcessorServiceClient.common_project_path(project)
+ assert expected == actual
+
+
+def test_parse_common_project_path():
+ expected = {
+ "project": "mussel",
+ }
+ path = DocumentProcessorServiceClient.common_project_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_project_path(path)
+ assert expected == actual
+
+
+def test_common_location_path():
+ project = "winkle"
+ location = "nautilus"
+
+ expected = "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+ actual = DocumentProcessorServiceClient.common_location_path(project, location)
+ assert expected == actual
+
+
+def test_parse_common_location_path():
+ expected = {
+ "project": "scallop",
+ "location": "abalone",
+ }
+ path = DocumentProcessorServiceClient.common_location_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_location_path(path)
+ assert expected == actual
+
+
+def test_client_withDEFAULT_CLIENT_INFO():
+ client_info = gapic_v1.client_info.ClientInfo()
+
+ with mock.patch.object(
+ transports.DocumentProcessorServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
+
+ with mock.patch.object(
+ transports.DocumentProcessorServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ transport_class = DocumentProcessorServiceClient.get_transport_class()
+ transport = transport_class(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
diff --git a/tests/unit/gapic/documentai_v1beta2/__init__.py b/tests/unit/gapic/documentai_v1beta2/__init__.py
index 8b137891..42ffdf2b 100644
--- a/tests/unit/gapic/documentai_v1beta2/__init__.py
+++ b/tests/unit/gapic/documentai_v1beta2/__init__.py
@@ -1 +1,16 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git a/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py b/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
index 290cb2f2..ffea17d6 100644
--- a/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
+++ b/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
@@ -101,7 +101,25 @@ def test__get_default_mtls_endpoint():
@pytest.mark.parametrize(
"client_class",
- [DocumentUnderstandingServiceClient, DocumentUnderstandingServiceAsyncClient],
+ [DocumentUnderstandingServiceClient, DocumentUnderstandingServiceAsyncClient,],
+)
+def test_document_understanding_service_client_from_service_account_info(client_class):
+ creds = credentials.AnonymousCredentials()
+ with mock.patch.object(
+ service_account.Credentials, "from_service_account_info"
+ ) as factory:
+ factory.return_value = creds
+ info = {"valid": True}
+ client = client_class.from_service_account_info(info)
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
+
+ assert client.transport._host == "us-documentai.googleapis.com:443"
+
+
+@pytest.mark.parametrize(
+ "client_class",
+ [DocumentUnderstandingServiceClient, DocumentUnderstandingServiceAsyncClient,],
)
def test_document_understanding_service_client_from_service_account_file(client_class):
creds = credentials.AnonymousCredentials()
@@ -110,17 +128,22 @@ def test_document_understanding_service_client_from_service_account_file(client_
) as factory:
factory.return_value = creds
client = client_class.from_service_account_file("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
client = client_class.from_service_account_json("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
- assert client._transport._host == "us-documentai.googleapis.com:443"
+ assert client.transport._host == "us-documentai.googleapis.com:443"
def test_document_understanding_service_client_get_transport_class():
transport = DocumentUnderstandingServiceClient.get_transport_class()
- assert transport == transports.DocumentUnderstandingServiceGrpcTransport
+ available_transports = [
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ ]
+ assert transport in available_transports
transport = DocumentUnderstandingServiceClient.get_transport_class("grpc")
assert transport == transports.DocumentUnderstandingServiceGrpcTransport
@@ -179,7 +202,7 @@ def test_document_understanding_service_client_client_options(
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -195,7 +218,7 @@ def test_document_understanding_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -211,7 +234,7 @@ def test_document_understanding_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_MTLS_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -239,7 +262,7 @@ def test_document_understanding_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id="octopus",
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -300,29 +323,25 @@ def test_document_understanding_service_client_mtls_env_auto(
client_cert_source=client_cert_source_callback
)
with mock.patch.object(transport_class, "__init__") as patched:
- ssl_channel_creds = mock.Mock()
- with mock.patch(
- "grpc.ssl_channel_credentials", return_value=ssl_channel_creds
- ):
- patched.return_value = None
- client = client_class(client_options=options)
+ patched.return_value = None
+ client = client_class(client_options=options)
- if use_client_cert_env == "false":
- expected_ssl_channel_creds = None
- expected_host = client.DEFAULT_ENDPOINT
- else:
- expected_ssl_channel_creds = ssl_channel_creds
- expected_host = client.DEFAULT_MTLS_ENDPOINT
+ if use_client_cert_env == "false":
+ expected_client_cert_source = None
+ expected_host = client.DEFAULT_ENDPOINT
+ else:
+ expected_client_cert_source = client_cert_source_callback
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=expected_host,
- scopes=None,
- ssl_channel_credentials=expected_ssl_channel_creds,
- quota_project_id=None,
- client_info=transports.base.DEFAULT_CLIENT_INFO,
- )
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=expected_host,
+ scopes=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
# Check the case ADC client cert is provided. Whether client cert is used depends on
# GOOGLE_API_USE_CLIENT_CERTIFICATE value.
@@ -331,66 +350,53 @@ def test_document_understanding_service_client_mtls_env_auto(
):
with mock.patch.object(transport_class, "__init__") as patched:
with mock.patch(
- "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=True,
):
with mock.patch(
- "google.auth.transport.grpc.SslCredentials.is_mtls",
- new_callable=mock.PropertyMock,
- ) as is_mtls_mock:
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.ssl_credentials",
- new_callable=mock.PropertyMock,
- ) as ssl_credentials_mock:
- if use_client_cert_env == "false":
- is_mtls_mock.return_value = False
- ssl_credentials_mock.return_value = None
- expected_host = client.DEFAULT_ENDPOINT
- expected_ssl_channel_creds = None
- else:
- is_mtls_mock.return_value = True
- ssl_credentials_mock.return_value = mock.Mock()
- expected_host = client.DEFAULT_MTLS_ENDPOINT
- expected_ssl_channel_creds = (
- ssl_credentials_mock.return_value
- )
-
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=expected_host,
- scopes=None,
- ssl_channel_credentials=expected_ssl_channel_creds,
- quota_project_id=None,
- client_info=transports.base.DEFAULT_CLIENT_INFO,
- )
+ "google.auth.transport.mtls.default_client_cert_source",
+ return_value=client_cert_source_callback,
+ ):
+ if use_client_cert_env == "false":
+ expected_host = client.DEFAULT_ENDPOINT
+ expected_client_cert_source = None
+ else:
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
+ expected_client_cert_source = client_cert_source_callback
- # Check the case client_cert_source and ADC client cert are not provided.
- with mock.patch.dict(
- os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
- ):
- with mock.patch.object(transport_class, "__init__") as patched:
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
- ):
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.is_mtls",
- new_callable=mock.PropertyMock,
- ) as is_mtls_mock:
- is_mtls_mock.return_value = False
patched.return_value = None
client = client_class()
patched.assert_called_once_with(
credentials=None,
credentials_file=None,
- host=client.DEFAULT_ENDPOINT,
+ host=expected_host,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
+ # Check the case client_cert_source and ADC client cert are not provided.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=False,
+ ):
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
@pytest.mark.parametrize(
"client_class,transport_class,transport_name",
@@ -420,7 +426,7 @@ def test_document_understanding_service_client_client_options_scopes(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=["1", "2"],
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -454,7 +460,7 @@ def test_document_understanding_service_client_client_options_credentials_file(
credentials_file="credentials.json",
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -473,7 +479,7 @@ def test_document_understanding_service_client_client_options_from_dict():
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -493,7 +499,7 @@ def test_batch_process_documents(
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/spam")
@@ -514,19 +520,40 @@ def test_batch_process_documents_from_dict():
test_batch_process_documents(request_type=dict)
+def test_batch_process_documents_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentUnderstandingServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ client.batch_process_documents()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_understanding.BatchProcessDocumentsRequest()
+
+
@pytest.mark.asyncio
-async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
+async def test_batch_process_documents_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_understanding.BatchProcessDocumentsRequest,
+):
client = DocumentUnderstandingServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = document_understanding.BatchProcessDocumentsRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
@@ -539,12 +566,17 @@ async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_understanding.BatchProcessDocumentsRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, future.Future)
+@pytest.mark.asyncio
+async def test_batch_process_documents_async_from_dict():
+ await test_batch_process_documents_async(request_type=dict)
+
+
def test_batch_process_documents_field_headers():
client = DocumentUnderstandingServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -557,7 +589,7 @@ def test_batch_process_documents_field_headers():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -586,7 +618,7 @@ async def test_batch_process_documents_field_headers_async():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
operations_pb2.Operation(name="operations/op")
@@ -611,7 +643,7 @@ def test_batch_process_documents_flattened():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -658,7 +690,7 @@ async def test_batch_process_documents_flattened_async():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -713,9 +745,7 @@ def test_process_document(
request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = document.Document(
mime_type="mime_type_value", text="text_value", uri="uri_value",
@@ -730,6 +760,7 @@ def test_process_document(
assert args[0] == document_understanding.ProcessDocumentRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, document.Document)
assert response.mime_type == "mime_type_value"
@@ -741,20 +772,37 @@ def test_process_document_from_dict():
test_process_document(request_type=dict)
+def test_process_document_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentUnderstandingServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ client.process_document()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_understanding.ProcessDocumentRequest()
+
+
@pytest.mark.asyncio
-async def test_process_document_async(transport: str = "grpc_asyncio"):
+async def test_process_document_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_understanding.ProcessDocumentRequest,
+):
client = DocumentUnderstandingServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = document_understanding.ProcessDocumentRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
document.Document(mime_type="mime_type_value", text="text_value",)
@@ -766,7 +814,7 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_understanding.ProcessDocumentRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, document.Document)
@@ -776,6 +824,11 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
assert response.text == "text_value"
+@pytest.mark.asyncio
+async def test_process_document_async_from_dict():
+ await test_process_document_async(request_type=dict)
+
+
def test_process_document_field_headers():
client = DocumentUnderstandingServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -787,9 +840,7 @@ def test_process_document_field_headers():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
call.return_value = document.Document()
client.process_document(request)
@@ -816,9 +867,7 @@ async def test_process_document_field_headers_async():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(document.Document())
await client.process_document(request)
@@ -869,7 +918,7 @@ def test_transport_instance():
credentials=credentials.AnonymousCredentials(),
)
client = DocumentUnderstandingServiceClient(transport=transport)
- assert client._transport is transport
+ assert client.transport is transport
def test_transport_get_channel():
@@ -908,7 +957,7 @@ def test_transport_grpc_default():
credentials=credentials.AnonymousCredentials(),
)
assert isinstance(
- client._transport, transports.DocumentUnderstandingServiceGrpcTransport,
+ client.transport, transports.DocumentUnderstandingServiceGrpcTransport,
)
@@ -1002,6 +1051,53 @@ def test_document_understanding_service_transport_auth_adc():
)
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_understanding_service_grpc_transport_client_cert_source_for_mtls(
+ transport_class,
+):
+ cred = credentials.AnonymousCredentials()
+
+ # Check ssl_channel_credentials is used if provided.
+ with mock.patch.object(transport_class, "create_channel") as mock_create_channel:
+ mock_ssl_channel_creds = mock.Mock()
+ transport_class(
+ host="squid.clam.whelk",
+ credentials=cred,
+ ssl_channel_credentials=mock_ssl_channel_creds,
+ )
+ mock_create_channel.assert_called_once_with(
+ "squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ ssl_credentials=mock_ssl_channel_creds,
+ quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
+ )
+
+ # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls
+ # is used.
+ with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()):
+ with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred:
+ transport_class(
+ credentials=cred,
+ client_cert_source_for_mtls=client_cert_source_callback,
+ )
+ expected_cert, expected_key = client_cert_source_callback()
+ mock_ssl_cred.assert_called_once_with(
+ certificate_chain=expected_cert, private_key=expected_key
+ )
+
+
def test_document_understanding_service_host_no_port():
client = DocumentUnderstandingServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -1009,7 +1105,7 @@ def test_document_understanding_service_host_no_port():
api_endpoint="us-documentai.googleapis.com"
),
)
- assert client._transport._host == "us-documentai.googleapis.com:443"
+ assert client.transport._host == "us-documentai.googleapis.com:443"
def test_document_understanding_service_host_with_port():
@@ -1019,11 +1115,11 @@ def test_document_understanding_service_host_with_port():
api_endpoint="us-documentai.googleapis.com:8000"
),
)
- assert client._transport._host == "us-documentai.googleapis.com:8000"
+ assert client.transport._host == "us-documentai.googleapis.com:8000"
def test_document_understanding_service_grpc_transport_channel():
- channel = grpc.insecure_channel("http://localhost/")
+ channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials())
# Check that channel is used if provided.
transport = transports.DocumentUnderstandingServiceGrpcTransport(
@@ -1031,10 +1127,11 @@ def test_document_understanding_service_grpc_transport_channel():
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
def test_document_understanding_service_grpc_asyncio_transport_channel():
- channel = aio.insecure_channel("http://localhost/")
+ channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials())
# Check that channel is used if provided.
transport = transports.DocumentUnderstandingServiceGrpcAsyncIOTransport(
@@ -1042,8 +1139,11 @@ def test_document_understanding_service_grpc_asyncio_transport_channel():
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.parametrize(
"transport_class",
[
@@ -1058,7 +1158,7 @@ def test_document_understanding_service_transport_channel_mtls_with_client_cert_
"grpc.ssl_channel_credentials", autospec=True
) as grpc_ssl_channel_cred:
with mock.patch.object(
- transport_class, "create_channel", autospec=True
+ transport_class, "create_channel"
) as grpc_create_channel:
mock_ssl_cred = mock.Mock()
grpc_ssl_channel_cred.return_value = mock_ssl_cred
@@ -1087,10 +1187,17 @@ def test_document_understanding_service_transport_channel_mtls_with_client_cert_
scopes=("https://www.googleapis.com/auth/cloud-platform",),
ssl_credentials=mock_ssl_cred,
quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
)
assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == mock_ssl_cred
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.parametrize(
"transport_class",
[
@@ -1108,7 +1215,7 @@ def test_document_understanding_service_transport_channel_mtls_with_adc(
ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
):
with mock.patch.object(
- transport_class, "create_channel", autospec=True
+ transport_class, "create_channel"
) as grpc_create_channel:
mock_grpc_channel = mock.Mock()
grpc_create_channel.return_value = mock_grpc_channel
@@ -1129,6 +1236,10 @@ def test_document_understanding_service_transport_channel_mtls_with_adc(
scopes=("https://www.googleapis.com/auth/cloud-platform",),
ssl_credentials=mock_ssl_cred,
quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
)
assert transport.grpc_channel == mock_grpc_channel
@@ -1137,7 +1248,7 @@ def test_document_understanding_service_grpc_lro_client():
client = DocumentUnderstandingServiceClient(
credentials=credentials.AnonymousCredentials(), transport="grpc",
)
- transport = client._transport
+ transport = client.transport
# Ensure that we have a api-core operations client.
assert isinstance(transport.operations_client, operations_v1.OperationsClient,)
@@ -1150,7 +1261,7 @@ def test_document_understanding_service_grpc_lro_async_client():
client = DocumentUnderstandingServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio",
)
- transport = client._client._transport
+ transport = client.transport
# Ensure that we have a api-core operations client.
assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,)
@@ -1159,6 +1270,109 @@ def test_document_understanding_service_grpc_lro_async_client():
assert transport.operations_client is transport.operations_client
+def test_common_billing_account_path():
+ billing_account = "squid"
+
+ expected = "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+ actual = DocumentUnderstandingServiceClient.common_billing_account_path(
+ billing_account
+ )
+ assert expected == actual
+
+
+def test_parse_common_billing_account_path():
+ expected = {
+ "billing_account": "clam",
+ }
+ path = DocumentUnderstandingServiceClient.common_billing_account_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentUnderstandingServiceClient.parse_common_billing_account_path(path)
+ assert expected == actual
+
+
+def test_common_folder_path():
+ folder = "whelk"
+
+ expected = "folders/{folder}".format(folder=folder,)
+ actual = DocumentUnderstandingServiceClient.common_folder_path(folder)
+ assert expected == actual
+
+
+def test_parse_common_folder_path():
+ expected = {
+ "folder": "octopus",
+ }
+ path = DocumentUnderstandingServiceClient.common_folder_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentUnderstandingServiceClient.parse_common_folder_path(path)
+ assert expected == actual
+
+
+def test_common_organization_path():
+ organization = "oyster"
+
+ expected = "organizations/{organization}".format(organization=organization,)
+ actual = DocumentUnderstandingServiceClient.common_organization_path(organization)
+ assert expected == actual
+
+
+def test_parse_common_organization_path():
+ expected = {
+ "organization": "nudibranch",
+ }
+ path = DocumentUnderstandingServiceClient.common_organization_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentUnderstandingServiceClient.parse_common_organization_path(path)
+ assert expected == actual
+
+
+def test_common_project_path():
+ project = "cuttlefish"
+
+ expected = "projects/{project}".format(project=project,)
+ actual = DocumentUnderstandingServiceClient.common_project_path(project)
+ assert expected == actual
+
+
+def test_parse_common_project_path():
+ expected = {
+ "project": "mussel",
+ }
+ path = DocumentUnderstandingServiceClient.common_project_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentUnderstandingServiceClient.parse_common_project_path(path)
+ assert expected == actual
+
+
+def test_common_location_path():
+ project = "winkle"
+ location = "nautilus"
+
+ expected = "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+ actual = DocumentUnderstandingServiceClient.common_location_path(project, location)
+ assert expected == actual
+
+
+def test_parse_common_location_path():
+ expected = {
+ "project": "scallop",
+ "location": "abalone",
+ }
+ path = DocumentUnderstandingServiceClient.common_location_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentUnderstandingServiceClient.parse_common_location_path(path)
+ assert expected == actual
+
+
def test_client_withDEFAULT_CLIENT_INFO():
client_info = gapic_v1.client_info.ClientInfo()
diff --git a/tests/unit/gapic/documentai_v1beta3/__init__.py b/tests/unit/gapic/documentai_v1beta3/__init__.py
index 8b137891..42ffdf2b 100644
--- a/tests/unit/gapic/documentai_v1beta3/__init__.py
+++ b/tests/unit/gapic/documentai_v1beta3/__init__.py
@@ -1 +1,16 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
diff --git a/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py b/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py
index 4b17a5ed..7179689c 100644
--- a/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py
+++ b/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py
@@ -45,6 +45,7 @@
transports,
)
from google.cloud.documentai_v1beta3.types import document
+from google.cloud.documentai_v1beta3.types import document_io
from google.cloud.documentai_v1beta3.types import document_processor_service
from google.cloud.documentai_v1beta3.types import geometry
from google.longrunning import operations_pb2
@@ -108,7 +109,25 @@ def test__get_default_mtls_endpoint():
@pytest.mark.parametrize(
"client_class",
- [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient],
+ [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient,],
+)
+def test_document_processor_service_client_from_service_account_info(client_class):
+ creds = credentials.AnonymousCredentials()
+ with mock.patch.object(
+ service_account.Credentials, "from_service_account_info"
+ ) as factory:
+ factory.return_value = creds
+ info = {"valid": True}
+ client = client_class.from_service_account_info(info)
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
+
+ assert client.transport._host == "us-documentai.googleapis.com:443"
+
+
+@pytest.mark.parametrize(
+ "client_class",
+ [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient,],
)
def test_document_processor_service_client_from_service_account_file(client_class):
creds = credentials.AnonymousCredentials()
@@ -117,17 +136,22 @@ def test_document_processor_service_client_from_service_account_file(client_clas
) as factory:
factory.return_value = creds
client = client_class.from_service_account_file("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
client = client_class.from_service_account_json("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
+ assert isinstance(client, client_class)
- assert client._transport._host == "us-documentai.googleapis.com:443"
+ assert client.transport._host == "us-documentai.googleapis.com:443"
def test_document_processor_service_client_get_transport_class():
transport = DocumentProcessorServiceClient.get_transport_class()
- assert transport == transports.DocumentProcessorServiceGrpcTransport
+ available_transports = [
+ transports.DocumentProcessorServiceGrpcTransport,
+ ]
+ assert transport in available_transports
transport = DocumentProcessorServiceClient.get_transport_class("grpc")
assert transport == transports.DocumentProcessorServiceGrpcTransport
@@ -186,7 +210,7 @@ def test_document_processor_service_client_client_options(
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -202,7 +226,7 @@ def test_document_processor_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -218,7 +242,7 @@ def test_document_processor_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_MTLS_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -246,7 +270,7 @@ def test_document_processor_service_client_client_options(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id="octopus",
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -307,29 +331,25 @@ def test_document_processor_service_client_mtls_env_auto(
client_cert_source=client_cert_source_callback
)
with mock.patch.object(transport_class, "__init__") as patched:
- ssl_channel_creds = mock.Mock()
- with mock.patch(
- "grpc.ssl_channel_credentials", return_value=ssl_channel_creds
- ):
- patched.return_value = None
- client = client_class(client_options=options)
+ patched.return_value = None
+ client = client_class(client_options=options)
- if use_client_cert_env == "false":
- expected_ssl_channel_creds = None
- expected_host = client.DEFAULT_ENDPOINT
- else:
- expected_ssl_channel_creds = ssl_channel_creds
- expected_host = client.DEFAULT_MTLS_ENDPOINT
+ if use_client_cert_env == "false":
+ expected_client_cert_source = None
+ expected_host = client.DEFAULT_ENDPOINT
+ else:
+ expected_client_cert_source = client_cert_source_callback
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=expected_host,
- scopes=None,
- ssl_channel_credentials=expected_ssl_channel_creds,
- quota_project_id=None,
- client_info=transports.base.DEFAULT_CLIENT_INFO,
- )
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=expected_host,
+ scopes=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
# Check the case ADC client cert is provided. Whether client cert is used depends on
# GOOGLE_API_USE_CLIENT_CERTIFICATE value.
@@ -338,66 +358,53 @@ def test_document_processor_service_client_mtls_env_auto(
):
with mock.patch.object(transport_class, "__init__") as patched:
with mock.patch(
- "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=True,
):
with mock.patch(
- "google.auth.transport.grpc.SslCredentials.is_mtls",
- new_callable=mock.PropertyMock,
- ) as is_mtls_mock:
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.ssl_credentials",
- new_callable=mock.PropertyMock,
- ) as ssl_credentials_mock:
- if use_client_cert_env == "false":
- is_mtls_mock.return_value = False
- ssl_credentials_mock.return_value = None
- expected_host = client.DEFAULT_ENDPOINT
- expected_ssl_channel_creds = None
- else:
- is_mtls_mock.return_value = True
- ssl_credentials_mock.return_value = mock.Mock()
- expected_host = client.DEFAULT_MTLS_ENDPOINT
- expected_ssl_channel_creds = (
- ssl_credentials_mock.return_value
- )
-
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=expected_host,
- scopes=None,
- ssl_channel_credentials=expected_ssl_channel_creds,
- quota_project_id=None,
- client_info=transports.base.DEFAULT_CLIENT_INFO,
- )
+ "google.auth.transport.mtls.default_client_cert_source",
+ return_value=client_cert_source_callback,
+ ):
+ if use_client_cert_env == "false":
+ expected_host = client.DEFAULT_ENDPOINT
+ expected_client_cert_source = None
+ else:
+ expected_host = client.DEFAULT_MTLS_ENDPOINT
+ expected_client_cert_source = client_cert_source_callback
- # Check the case client_cert_source and ADC client cert are not provided.
- with mock.patch.dict(
- os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
- ):
- with mock.patch.object(transport_class, "__init__") as patched:
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
- ):
- with mock.patch(
- "google.auth.transport.grpc.SslCredentials.is_mtls",
- new_callable=mock.PropertyMock,
- ) as is_mtls_mock:
- is_mtls_mock.return_value = False
patched.return_value = None
client = client_class()
patched.assert_called_once_with(
credentials=None,
credentials_file=None,
- host=client.DEFAULT_ENDPOINT,
+ host=expected_host,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=expected_client_cert_source,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
+ # Check the case client_cert_source and ADC client cert are not provided.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.mtls.has_default_client_cert_source",
+ return_value=False,
+ ):
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ client_cert_source_for_mtls=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
@pytest.mark.parametrize(
"client_class,transport_class,transport_name",
@@ -427,7 +434,7 @@ def test_document_processor_service_client_client_options_scopes(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=["1", "2"],
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -461,7 +468,7 @@ def test_document_processor_service_client_client_options_credentials_file(
credentials_file="credentials.json",
host=client.DEFAULT_ENDPOINT,
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -480,7 +487,7 @@ def test_document_processor_service_client_client_options_from_dict():
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- ssl_channel_credentials=None,
+ client_cert_source_for_mtls=None,
quota_project_id=None,
client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -498,9 +505,7 @@ def test_process_document(
request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = document_processor_service.ProcessResponse(
human_review_operation="human_review_operation_value",
@@ -515,6 +520,7 @@ def test_process_document(
assert args[0] == document_processor_service.ProcessRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, document_processor_service.ProcessResponse)
assert response.human_review_operation == "human_review_operation_value"
@@ -524,20 +530,37 @@ def test_process_document_from_dict():
test_process_document(request_type=dict)
+def test_process_document_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
+ client.process_document()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ProcessRequest()
+
+
@pytest.mark.asyncio
-async def test_process_document_async(transport: str = "grpc_asyncio"):
+async def test_process_document_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.ProcessRequest,
+):
client = DocumentProcessorServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = document_processor_service.ProcessRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
document_processor_service.ProcessResponse(
@@ -551,7 +574,7 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_processor_service.ProcessRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, document_processor_service.ProcessResponse)
@@ -559,6 +582,11 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
assert response.human_review_operation == "human_review_operation_value"
+@pytest.mark.asyncio
+async def test_process_document_async_from_dict():
+ await test_process_document_async(request_type=dict)
+
+
def test_process_document_field_headers():
client = DocumentProcessorServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -570,9 +598,7 @@ def test_process_document_field_headers():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
call.return_value = document_processor_service.ProcessResponse()
client.process_document(request)
@@ -599,9 +625,7 @@ async def test_process_document_field_headers_async():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
document_processor_service.ProcessResponse()
)
@@ -624,9 +648,7 @@ def test_process_document_flattened():
)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = document_processor_service.ProcessResponse()
@@ -662,9 +684,7 @@ async def test_process_document_flattened_async():
)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.process_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.process_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = document_processor_service.ProcessResponse()
@@ -710,7 +730,7 @@ def test_batch_process_documents(
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/spam")
@@ -731,19 +751,40 @@ def test_batch_process_documents_from_dict():
test_batch_process_documents(request_type=dict)
+def test_batch_process_documents_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.batch_process_documents), "__call__"
+ ) as call:
+ client.batch_process_documents()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.BatchProcessRequest()
+
+
@pytest.mark.asyncio
-async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
+async def test_batch_process_documents_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.BatchProcessRequest,
+):
client = DocumentProcessorServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = document_processor_service.BatchProcessRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
@@ -756,12 +797,17 @@ async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_processor_service.BatchProcessRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, future.Future)
+@pytest.mark.asyncio
+async def test_batch_process_documents_async_from_dict():
+ await test_batch_process_documents_async(request_type=dict)
+
+
def test_batch_process_documents_field_headers():
client = DocumentProcessorServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -774,7 +820,7 @@ def test_batch_process_documents_field_headers():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -803,7 +849,7 @@ async def test_batch_process_documents_field_headers_async():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
operations_pb2.Operation(name="operations/op")
@@ -828,7 +874,7 @@ def test_batch_process_documents_flattened():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -866,7 +912,7 @@ async def test_batch_process_documents_flattened_async():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.batch_process_documents), "__call__"
+ type(client.transport.batch_process_documents), "__call__"
) as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -913,7 +959,7 @@ def test_review_document(
request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.review_document), "__call__") as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/spam")
@@ -933,20 +979,37 @@ def test_review_document_from_dict():
test_review_document(request_type=dict)
+def test_review_document_empty_call():
+ # This test is a coverage failsafe to make sure that totally empty calls,
+ # i.e. request == None and no flattened fields passed, work.
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
+ client.review_document()
+ call.assert_called()
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
+
+
@pytest.mark.asyncio
-async def test_review_document_async(transport: str = "grpc_asyncio"):
+async def test_review_document_async(
+ transport: str = "grpc_asyncio",
+ request_type=document_processor_service.ReviewDocumentRequest,
+):
client = DocumentProcessorServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = document_processor_service.ReviewDocumentRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.review_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
operations_pb2.Operation(name="operations/spam")
@@ -958,12 +1021,17 @@ async def test_review_document_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, future.Future)
+@pytest.mark.asyncio
+async def test_review_document_async_from_dict():
+ await test_review_document_async(request_type=dict)
+
+
def test_review_document_field_headers():
client = DocumentProcessorServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -975,7 +1043,7 @@ def test_review_document_field_headers():
request.human_review_config = "human_review_config/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.review_document), "__call__") as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
call.return_value = operations_pb2.Operation(name="operations/op")
client.review_document(request)
@@ -1005,9 +1073,7 @@ async def test_review_document_field_headers_async():
request.human_review_config = "human_review_config/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.review_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
operations_pb2.Operation(name="operations/op")
)
@@ -1033,7 +1099,7 @@ def test_review_document_flattened():
)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.review_document), "__call__") as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -1070,9 +1136,7 @@ async def test_review_document_flattened_async():
)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.review_document), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.review_document), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = operations_pb2.Operation(name="operations/op")
@@ -1144,7 +1208,7 @@ def test_transport_instance():
credentials=credentials.AnonymousCredentials(),
)
client = DocumentProcessorServiceClient(transport=transport)
- assert client._transport is transport
+ assert client.transport is transport
def test_transport_get_channel():
@@ -1183,7 +1247,7 @@ def test_transport_grpc_default():
credentials=credentials.AnonymousCredentials(),
)
assert isinstance(
- client._transport, transports.DocumentProcessorServiceGrpcTransport,
+ client.transport, transports.DocumentProcessorServiceGrpcTransport,
)
@@ -1278,6 +1342,53 @@ def test_document_processor_service_transport_auth_adc():
)
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_service_grpc_transport_client_cert_source_for_mtls(
+ transport_class,
+):
+ cred = credentials.AnonymousCredentials()
+
+ # Check ssl_channel_credentials is used if provided.
+ with mock.patch.object(transport_class, "create_channel") as mock_create_channel:
+ mock_ssl_channel_creds = mock.Mock()
+ transport_class(
+ host="squid.clam.whelk",
+ credentials=cred,
+ ssl_channel_credentials=mock_ssl_channel_creds,
+ )
+ mock_create_channel.assert_called_once_with(
+ "squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ ssl_credentials=mock_ssl_channel_creds,
+ quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
+ )
+
+ # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls
+ # is used.
+ with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()):
+ with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred:
+ transport_class(
+ credentials=cred,
+ client_cert_source_for_mtls=client_cert_source_callback,
+ )
+ expected_cert, expected_key = client_cert_source_callback()
+ mock_ssl_cred.assert_called_once_with(
+ certificate_chain=expected_cert, private_key=expected_key
+ )
+
+
def test_document_processor_service_host_no_port():
client = DocumentProcessorServiceClient(
credentials=credentials.AnonymousCredentials(),
@@ -1285,7 +1396,7 @@ def test_document_processor_service_host_no_port():
api_endpoint="us-documentai.googleapis.com"
),
)
- assert client._transport._host == "us-documentai.googleapis.com:443"
+ assert client.transport._host == "us-documentai.googleapis.com:443"
def test_document_processor_service_host_with_port():
@@ -1295,11 +1406,11 @@ def test_document_processor_service_host_with_port():
api_endpoint="us-documentai.googleapis.com:8000"
),
)
- assert client._transport._host == "us-documentai.googleapis.com:8000"
+ assert client.transport._host == "us-documentai.googleapis.com:8000"
def test_document_processor_service_grpc_transport_channel():
- channel = grpc.insecure_channel("http://localhost/")
+ channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials())
# Check that channel is used if provided.
transport = transports.DocumentProcessorServiceGrpcTransport(
@@ -1307,10 +1418,11 @@ def test_document_processor_service_grpc_transport_channel():
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
def test_document_processor_service_grpc_asyncio_transport_channel():
- channel = aio.insecure_channel("http://localhost/")
+ channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials())
# Check that channel is used if provided.
transport = transports.DocumentProcessorServiceGrpcAsyncIOTransport(
@@ -1318,8 +1430,11 @@ def test_document_processor_service_grpc_asyncio_transport_channel():
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.parametrize(
"transport_class",
[
@@ -1334,7 +1449,7 @@ def test_document_processor_service_transport_channel_mtls_with_client_cert_sour
"grpc.ssl_channel_credentials", autospec=True
) as grpc_ssl_channel_cred:
with mock.patch.object(
- transport_class, "create_channel", autospec=True
+ transport_class, "create_channel"
) as grpc_create_channel:
mock_ssl_cred = mock.Mock()
grpc_ssl_channel_cred.return_value = mock_ssl_cred
@@ -1363,10 +1478,17 @@ def test_document_processor_service_transport_channel_mtls_with_client_cert_sour
scopes=("https://www.googleapis.com/auth/cloud-platform",),
ssl_credentials=mock_ssl_cred,
quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
)
assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == mock_ssl_cred
+# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are
+# removed from grpc/grpc_asyncio transport constructor.
@pytest.mark.parametrize(
"transport_class",
[
@@ -1382,7 +1504,7 @@ def test_document_processor_service_transport_channel_mtls_with_adc(transport_cl
ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
):
with mock.patch.object(
- transport_class, "create_channel", autospec=True
+ transport_class, "create_channel"
) as grpc_create_channel:
mock_grpc_channel = mock.Mock()
grpc_create_channel.return_value = mock_grpc_channel
@@ -1403,6 +1525,10 @@ def test_document_processor_service_transport_channel_mtls_with_adc(transport_cl
scopes=("https://www.googleapis.com/auth/cloud-platform",),
ssl_credentials=mock_ssl_cred,
quota_project_id=None,
+ options=[
+ ("grpc.max_send_message_length", -1),
+ ("grpc.max_receive_message_length", -1),
+ ],
)
assert transport.grpc_channel == mock_grpc_channel
@@ -1411,7 +1537,7 @@ def test_document_processor_service_grpc_lro_client():
client = DocumentProcessorServiceClient(
credentials=credentials.AnonymousCredentials(), transport="grpc",
)
- transport = client._transport
+ transport = client.transport
# Ensure that we have a api-core operations client.
assert isinstance(transport.operations_client, operations_v1.OperationsClient,)
@@ -1424,7 +1550,7 @@ def test_document_processor_service_grpc_lro_async_client():
client = DocumentProcessorServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio",
)
- transport = client._client._transport
+ transport = client.transport
# Ensure that we have a api-core operations client.
assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,)
@@ -1433,6 +1559,159 @@ def test_document_processor_service_grpc_lro_async_client():
assert transport.operations_client is transport.operations_client
+def test_human_review_config_path():
+ project = "squid"
+ location = "clam"
+ processor = "whelk"
+
+ expected = "projects/{project}/locations/{location}/processors/{processor}/humanReviewConfig".format(
+ project=project, location=location, processor=processor,
+ )
+ actual = DocumentProcessorServiceClient.human_review_config_path(
+ project, location, processor
+ )
+ assert expected == actual
+
+
+def test_parse_human_review_config_path():
+ expected = {
+ "project": "octopus",
+ "location": "oyster",
+ "processor": "nudibranch",
+ }
+ path = DocumentProcessorServiceClient.human_review_config_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_human_review_config_path(path)
+ assert expected == actual
+
+
+def test_processor_path():
+ project = "cuttlefish"
+ location = "mussel"
+ processor = "winkle"
+
+ expected = "projects/{project}/locations/{location}/processors/{processor}".format(
+ project=project, location=location, processor=processor,
+ )
+ actual = DocumentProcessorServiceClient.processor_path(project, location, processor)
+ assert expected == actual
+
+
+def test_parse_processor_path():
+ expected = {
+ "project": "nautilus",
+ "location": "scallop",
+ "processor": "abalone",
+ }
+ path = DocumentProcessorServiceClient.processor_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_processor_path(path)
+ assert expected == actual
+
+
+def test_common_billing_account_path():
+ billing_account = "squid"
+
+ expected = "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+ actual = DocumentProcessorServiceClient.common_billing_account_path(billing_account)
+ assert expected == actual
+
+
+def test_parse_common_billing_account_path():
+ expected = {
+ "billing_account": "clam",
+ }
+ path = DocumentProcessorServiceClient.common_billing_account_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_billing_account_path(path)
+ assert expected == actual
+
+
+def test_common_folder_path():
+ folder = "whelk"
+
+ expected = "folders/{folder}".format(folder=folder,)
+ actual = DocumentProcessorServiceClient.common_folder_path(folder)
+ assert expected == actual
+
+
+def test_parse_common_folder_path():
+ expected = {
+ "folder": "octopus",
+ }
+ path = DocumentProcessorServiceClient.common_folder_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_folder_path(path)
+ assert expected == actual
+
+
+def test_common_organization_path():
+ organization = "oyster"
+
+ expected = "organizations/{organization}".format(organization=organization,)
+ actual = DocumentProcessorServiceClient.common_organization_path(organization)
+ assert expected == actual
+
+
+def test_parse_common_organization_path():
+ expected = {
+ "organization": "nudibranch",
+ }
+ path = DocumentProcessorServiceClient.common_organization_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_organization_path(path)
+ assert expected == actual
+
+
+def test_common_project_path():
+ project = "cuttlefish"
+
+ expected = "projects/{project}".format(project=project,)
+ actual = DocumentProcessorServiceClient.common_project_path(project)
+ assert expected == actual
+
+
+def test_parse_common_project_path():
+ expected = {
+ "project": "mussel",
+ }
+ path = DocumentProcessorServiceClient.common_project_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_project_path(path)
+ assert expected == actual
+
+
+def test_common_location_path():
+ project = "winkle"
+ location = "nautilus"
+
+ expected = "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+ actual = DocumentProcessorServiceClient.common_location_path(project, location)
+ assert expected == actual
+
+
+def test_parse_common_location_path():
+ expected = {
+ "project": "scallop",
+ "location": "abalone",
+ }
+ path = DocumentProcessorServiceClient.common_location_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = DocumentProcessorServiceClient.parse_common_location_path(path)
+ assert expected == actual
+
+
def test_client_withDEFAULT_CLIENT_INFO():
client_info = gapic_v1.client_info.ClientInfo()
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy