How to extract the [Documentation] text from Robot framework test case - api

I am trying to extract the content of the [Documentation] section as a string for comparision with other part in a Python script.
I was told to use Robot framework API https://robot-framework.readthedocs.io/en/stable/
to extract but I have no idea how.
However, I am required to work with version 3.1.2
Example:
*** Test Cases ***
ATC Verify that Sensor Battery can enable and disable manufacturing mode
[Documentation] E1: This is the description of the test 1
... E2: This is the description of the test 2
[Tags] E1 TRACE{Trace_of_E1}
... E2 TRACE{Trace_of_E2}
Extract the string as
E1: This is the description of the test 1
E2: This is the description of the test 2

Have a look at these examples. I did something similar to generate testplans descritio. I tried to adapt my code to your requirements and this could maybe work for you.
import os
import re
from robot.api.parsing import (
get_model, get_tokens, Documentation, EmptyLine, KeywordCall,
ModelVisitor, Token
)
class RobotParser(ModelVisitor):
def __init__(self):
# Create object with remarkup_text to store formated documentation
self.text = ''
def get_text(self):
return self.text
def visit_TestCase(self, node):
# The matched `TestCase` node is a block with `header` and
# `body` attributes. `header` is a statement with familiar
# `get_token` and `get_value` methods for getting certain
# tokens or their value.
for keyword in node.body:
# skip empty lines
if keyword.get_value(Token.DOCUMENTATION) == None:
continue
self.text += keyword.get_value(Token.ARGUMENT)
def visit_Documentation(self,node):
# The matched "Documentation" node with value
self.remarkup_text += node.value + self.new_line
def visit_File(self, node):
# Call `generic_visit` to visit also child nodes.
return self.generic_visit(node)
if __name__ == "__main__":
path = "../tests"
for filename in os.listdir(path):
if re.match(".*\.robot", filename):
model = get_model(os.path.join(path, filename))
robot_parser = RobotParser()
robot_parser.visit(model)
text=robot_parser._text()

The code marked as best answer didn't quite work for me and has a lot of redundancy but it inspired me enough to get into the parsing and write it in a much readable and efficient way that actually works as is. You just have to have your own way of generating & iterating through filesystem where you call the get_robot_metadata(filepath) function.
from robot.api.parsing import (get_model, ModelVisitor, Token)
class RobotParser(ModelVisitor):
def __init__(self):
self.testcases = {}
def visit_TestCase(self, node):
testcasename = (node.header.name)
self.testcases[testcasename] = {}
for section in node.body:
if section.get_value(Token.DOCUMENTATION) != None:
documentation = section.value
self.testcases[testcasename]['Documentation'] = documentation
elif section.get_value(Token.TAGS) != None:
tags = section.values
self.testcases[testcasename]['Tags'] = tags
def get_testcases(self):
return self.testcases
def get_robot_metadata(filepath):
if filepath.endswith('.robot'):
robot_parser = RobotParser()
model = get_model(filepath)
robot_parser.visit(model)
metadata = robot_parser.get_testcases()
return metadata

This function will be able to extract the [Documentation] section from the testcase:
def documentation_extractor(testcase):
documentation = []
for setting in testcase.settings:
if len(setting) > 2 and setting[1].lower() == "[documentation]":
for doc in setting[2:]:
if doc.startswith("#"):
# the start of a comment, so skip rest of the line
break
documentation.append(doc)
break
return "\n".join(documentation)

Related

Vertex AI Model Batch prediction, issue with referencing existing model and input file on Cloud Storage

I'm struggling to correctly set Vertex AI pipeline which does the following:
read data from API and store to GCS and as as input for batch prediction.
get an existing model (Video classification on Vertex AI)
create Batch prediction job with input from point 1.
As it will be seen, I don't have much experience with Vertex Pipelines/Kubeflow thus I'm asking for help/advice, hope it's just some beginner mistake.
this is the gist of the code I'm using as pipeline
from google_cloud_pipeline_components import aiplatform as gcc_aip
from kfp.v2 import dsl
from kfp.v2.dsl import component
from kfp.v2.dsl import (
Output,
Artifact,
Model,
)
PROJECT_ID = 'my-gcp-project'
BUCKET_NAME = "mybucket"
PIPELINE_ROOT = "{}/pipeline_root".format(BUCKET_NAME)
#component
def get_input_data() -> str:
# getting data from API, save to Cloud Storage
# return GS URI
gcs_batch_input_path = 'gs://somebucket/file'
return gcs_batch_input_path
#component(
base_image="python:3.9",
packages_to_install=['google-cloud-aiplatform==1.8.0']
)
def load_ml_model(project_id: str, model: Output[Artifact]):
"""Load existing Vertex model"""
import google.cloud.aiplatform as aip
model_id = '1234'
model = aip.Model(model_name=model_id, project=project_id, location='us-central1')
#dsl.pipeline(
name="batch-pipeline", pipeline_root=PIPELINE_ROOT,
)
def pipeline(gcp_project: str):
input_data = get_input_data()
ml_model = load_ml_model(gcp_project)
gcc_aip.ModelBatchPredictOp(
project=PROJECT_ID,
job_display_name=f'test-prediction',
model=ml_model.output,
gcs_source_uris=[input_data.output], # this doesn't work
# gcs_source_uris=['gs://mybucket/output/'], # hardcoded gs uri works
gcs_destination_output_uri_prefix=f'gs://{PIPELINE_ROOT}/prediction_output/'
)
if __name__ == '__main__':
from kfp.v2 import compiler
import google.cloud.aiplatform as aip
pipeline_export_filepath = 'test-pipeline.json'
compiler.Compiler().compile(pipeline_func=pipeline,
package_path=pipeline_export_filepath)
# pipeline_params = {
# 'gcp_project': PROJECT_ID,
# }
# job = aip.PipelineJob(
# display_name='test-pipeline',
# template_path=pipeline_export_filepath,
# pipeline_root=f'gs://{PIPELINE_ROOT}',
# project=PROJECT_ID,
# parameter_values=pipeline_params,
# )
# job.run()
When running the pipeline it throws this exception when running Batch prediction:
details = "List of found errors: 1.Field: batch_prediction_job.model; Message: Invalid Model resource name.
so I'm not sure what could be wrong. I tried to load model in the notebook (outside of component) and it correctly returns.
Second issue I'm having is referencing GCS URI as output from component to batch job input.
input_data = get_input_data2()
gcc_aip.ModelBatchPredictOp(
project=PROJECT_ID,
job_display_name=f'test-prediction',
model=ml_model.output,
gcs_source_uris=[input_data.output], # this doesn't work
# gcs_source_uris=['gs://mybucket/output/'], # hardcoded gs uri works
gcs_destination_output_uri_prefix=f'gs://{PIPELINE_ROOT}/prediction_output/'
)
During compilation, I get following exception TypeError: Object of type PipelineParam is not JSON serializable, though I think this could be issue of ModelBatchPredictOp component.
Again any help/advice appreciated, I'm dealing with this from yesterday, so maybe I missed something obvious.
libraries I'm using:
google-cloud-aiplatform==1.8.0
google-cloud-pipeline-components==0.2.0
kfp==1.8.10
kfp-pipeline-spec==0.1.13
kfp-server-api==1.7.1
UPDATE
After comments, some research and tuning, for referencing model this works:
#component
def load_ml_model(project_id: str, model: Output[Artifact]):
region = 'us-central1'
model_id = '1234'
model_uid = f'projects/{project_id}/locations/{region}/models/{model_id}'
model.uri = model_uid
model.metadata['resourceName'] = model_uid
and then I can use it as intended:
batch_predict_op = gcc_aip.ModelBatchPredictOp(
project=gcp_project,
job_display_name=f'batch-prediction-test',
model=ml_model.outputs['model'],
gcs_source_uris=[input_batch_gcs_path],
gcs_destination_output_uri_prefix=f'gs://{BUCKET_NAME}/prediction_output/test'
)
UPDATE 2
regarding GCS path, a workaround is to define path outside of the component and pass it as an input parameter, for example (abbreviated):
#dsl.pipeline(
name="my-pipeline",
pipeline_root=PIPELINE_ROOT,
)
def pipeline(
gcp_project: str,
region: str,
bucket: str
):
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
gcs_prediction_input_path = f'gs://{BUCKET_NAME}/prediction_input/video_batch_prediction_input_{ts}.jsonl'
batch_input_data_op = get_input_data(gcs_prediction_input_path) # this loads input data to GCS path
batch_predict_op = gcc_aip.ModelBatchPredictOp(
project=gcp_project,
model=training_job_run_op.outputs["model"],
job_display_name='batch-prediction',
# gcs_source_uris=[batch_input_data_op.output],
gcs_source_uris=[gcs_prediction_input_path],
gcs_destination_output_uri_prefix=f'gs://{BUCKET_NAME}/prediction_output/',
).after(batch_input_data_op) # we need to add 'after' so it runs after input data is prepared since get_input_data doesn't returns anything
still not sure, why it doesn't work/compile when I return GCS path from get_input_data component
I'm glad you solved most of your main issues and found a workaround for model declaration.
For your input.output observation on gcs_source_uris, the reason behind it is because the way the function/class returns the value. If you dig inside the class/methods of google_cloud_pipeline_components you will find that it implements a structure that will allow you to use .outputs from the returned value of the function called.
If you go to the implementation of one of the components of the pipeline you will find that it returns an output array from convert_method_to_component function. So, in order to have that implemented in your custom class/function your function should return a value which can be called as an attribute. Below is a basic implementation of it.
class CustomClass():
def __init__(self):
self.return_val = {'path':'custompath','desc':'a desc'}
#property
def output(self):
return self.return_val
hello = CustomClass()
print(hello.output['path'])
If you want to dig more about it you can go to the following pages:
convert_method_to_component, which is the implementation of convert_method_to_component
Properties, basics of property in python.

insert_many in pymongo not persisting

I'm having some issues with persisting documents with pymongo when using insert_many.
I'm handing over a list of dicts to insert_many and it works fine from inside the same script that does the inserting. Less so once the script has finished.
def row_to_doc(row):
rowdict = row.to_dict()
for key in rowdict:
val = rowdict[key]
if type(val) == float or type(val) == np.float64:
if np.isnan(val):
# If we want a SQL style document collection
rowdict[key] = None
# If we want a NoSQL style document collection
# del rowdict[key]
return rowdict
def dataframe_to_collection(df):
n = len(df)
doc_list = []
for k in range(n):
doc_list.append(row_to_doc(df.iloc[k]))
return doc_list
def get_mongodb_client(host="localhost", port=27017):
return MongoClient(host, port)
def create_collection(client):
db = client["material"]
return db["master-data"]
def add_docs_to_mongo(collection, doc_list):
collection.insert_many(doc_list)
def main():
client = get_mongodb_client()
csv_fname = "some_csv_fname.csv"
df = get_clean_csv(csv_fname)
doc_list = dataframe_to_collection(df)
collection = create_collection(client)
add_docs_to_mongo(collection, doc_list)
test_doc = collection.find_one({"MATERIAL": "000000000000000001"})
When I open up another python REPL and start looking through the client.material.master_data collection with collection.find_one({"MATERIAL": "000000000000000001"}) or collection.count_documents({}) I get None for the find_one and 0 for the count_documents.
Is there a step where I need to call some method to persist the data to disk? db.collection.save() in the mongo client API sounds like what I need but it's just another way of inserting documents from what I have read. Any help would be greatly appreciated.
The problem was that I was getting my collection via client.db_name.collection_name and it wasn't getting the same collection I was creating with my code. client.db_name["collection-name"] solved my issue. Weird.

apollo-upload-client and graphene-django

I have a question about using apollo-upload-client and graphene-django. Here I've discovered that apollo-upload-client adding operations to formData. But here graphene-django is only trying to get query parameter. And the question is, where and how it should be fixed?
If you're referring to the data that has a header like (when viewing the HTTP from Chrome tools):
Content-Disposition: form-data; name="operations"
and data like
{"operationName":"MyMutation","variables":{"myData"....}, "query":"mutation MyMutation"...},
the graphene-python library interprets this and assembles it into a query for you, inserting the variables and removing the file data from the query. If you are using Django, you can find all of the uploaded files in info.context.FILES when writing a mutation.
Here's my solution to support the latest apollo-upload-client (8.1). I recently had to revisit my Django code when I upgraded from apollo-upload-client 5.x to 8.x. Hope this helps.
Sorry I'm using an older graphene-django but hopefully you can update the mutation syntax to the latest.
Upload scalar type (passthrough, basically):
class Upload(Scalar):
'''A file upload'''
#staticmethod
def serialize(value):
raise Exception('File upload cannot be serialized')
#staticmethod
def parse_literal(node):
raise Exception('No such thing as a file upload literal')
#staticmethod
def parse_value(value):
return value
My upload mutation:
class UploadImage(relay.ClientIDMutation):
class Input:
image = graphene.Field(Upload, required=True)
success = graphene.Field(graphene.Boolean)
#classmethod
def mutate_and_get_payload(cls, input, context, info):
with NamedTemporaryFile(delete=False) as tmp:
for chunk in input['image'].chunks():
tmp.write(chunk)
image_file = tmp.name
# do something with image_file
return UploadImage(success=True)
The heavy lifting happens in a custom GraphQL view. Basically it injects the file object into the appropriate places in the variables map.
def maybe_int(s):
try:
return int(s)
except ValueError:
return s
class CustomGraphqlView(GraphQLView):
def parse_request_json(self, json_string):
try:
request_json = json.loads(json_string)
if self.batch:
assert isinstance(request_json,
list), ('Batch requests should receive a list, but received {}.').format(
repr(request_json))
assert len(request_json) > 0, ('Received an empty list in the batch request.')
else:
assert isinstance(request_json, dict), ('The received data is not a valid JSON query.')
return request_json
except AssertionError as e:
raise HttpError(HttpResponseBadRequest(str(e)))
except BaseException:
logger.exception('Invalid JSON')
raise HttpError(HttpResponseBadRequest('POST body sent invalid JSON.'))
def parse_body(self, request):
content_type = self.get_content_type(request)
if content_type == 'application/graphql':
return {'query': request.body.decode()}
elif content_type == 'application/json':
return self.parse_request_json(request.body.decode('utf-8'))
elif content_type in ['application/x-www-form-urlencoded', 'multipart/form-data']:
operations_json = request.POST.get('operations')
map_json = request.POST.get('map')
if operations_json and map_json:
operations = self.parse_request_json(operations_json)
map = self.parse_request_json(map_json)
for file_id, f in request.FILES.items():
for name in map[file_id]:
segments = [maybe_int(s) for s in name.split('.')]
cur = operations
while len(segments) > 1:
cur = cur[segments.pop(0)]
cur[segments.pop(0)] = f
logger.info('parse_body %s', operations)
return operations
else:
return request.POST
return {}

tkinter variable for drop down selection empty

I tried to program an app in tkinter that would load random lines from a file you select from a pull down menu and display the selected line in a text window.
It seems like the variable "var" in insert_text does not return the selected "option" but rather an "empty" string resulting in a the following error:
"File not found error" (FileNotFoundError: [Errno2] No such file or
directory: '').
Please help!
#!/usr/bin/env python
# Python 3
import tkinter
from tkinter import ttk
import random
class Application:
def __init__(self, root):
self.root = root
self.root.title('Random Stuff')
ttk.Frame(self.root, width=450, height=185).pack()
self.init_widgets()
var = tkinter.StringVar(root)
script = var.get()
choices = ['option1', 'option2', 'option3']
option = tkinter.OptionMenu(root, var, *choices)
option.pack(side='right', padx=10, pady=10)
def init_widgets(self):
ttk.Button(self.root, command=self.insert_txt, text='Button', width='10').place(x=10, y=10)
self.txt = tkinter.Text(self.root, width='45', height='5')
self.txt.place(x=10, y=50)
def insert_txt(self):
var = tkinter.StringVar(root)
name = var.get()
line = random.choice(open(str(name)).readlines())
self.txt.insert(tkinter.INSERT, line)
if __name__ == '__main__':
root = tkinter.Tk()
Application(root)
root.mainloop()
That's because you're just creating an empty StringVar that isn't modified later, thus returning an empty string.
The OptionMenu takes the command parameter that calls the specified method every time another option is selected. Now, you can call a method like this, replacing you insert_txt:
def __init__(self):
# ...
self.var = tkinter.StringVar()
self.options = tkinter.OptionMenu(root, var, *choices, command=self.option_selected)
# ...
def option_selected(self, event):
name = self.var.get()
# The stuff you already had
Additionally, you have to empty the Text widget, otherwise the previous text would stay. I think the Entry widget is better for that, too.

SoapUI API - Setting TestCase property

I'm trying to use SoapUI API in jython (modifying SoapUILibrary for Robot Framework) and somehow i am not able to find any way to set property of a Property TestStep.
Setting project, global and system properties is easy using
SoapUITestCaseRunner.setProjectProperties()
from com.eviware.soapui.tools import (SoapUITestCaseRunner)
from com.eviware.soapui.tools import (SoapUIMockServiceRunner)
from robot.api import logger
class SoapUILibrary2:
""" The main class of the library """
ROBOT_LIBRARY_SCOPE = 'TEST CASE'
ROBOT_LIBRARY_VERSION = '0.2'
def __init__(self):
self.__runner = None
self.__mockrunner = None
self._project_properties = []
def soapui_project(self, prj):
""" Initialize the runner and set the project string """
self.__runner = SoapUITestCaseRunner()
self.__runner.setProjectFile(prj)
def soapui_suite(self, s):
""" Set the suite string """
self.__runner.setTestSuite(s)
def soapui_case(self, c):
""" Set the test case string """
self.__runner.setTestCase(c)
def soapui_set_project_property(self, *properties):
""" Sets project properties for the current test run. (...)
"""
for prop in properties:
if len(prop.split('=')) == 2:
self._project_properties.append(prop)
else:
logger.warn("Skipping property: '%s'. Properties must be specified as: key=value" % prop)
try:
self.__runner.setProjectProperties(self._project_properties)
except AttributeError:
logger.warn('No project set. Cannot set project properties.')
SoapUITestCaseRunner class does not contain any way to access testSteps...
I found some examples how to solve this issue in groovy, however is it possible to set such properties using SoapUI API?
EDIT:
Adding whole code of the library. It's made to be imported in robot framework and used as its keywords.
http://tny.cz/34882261
In SOAPUI you can define properties for project, testCase and testSuite, it's also possible to generate a special type of testStep (Properties TestStep) but you can't define properties on a specific testStep (i.e on SOAP TestStep). You can see more info about here
However you can use a properties from project, testCase or testSuite in your testSteps so i.e you can define a property in a project level and then use it in your testStep. I can't give more specific info because I don't know exactly what you're trying to achieve.
EDIT:
I don't know the specific jython syntax but If you have the project file (as I see in your sample) you can access a specific testStep through com.eviware.soapui.impl.wsdl.WsdlProject, I give you a groovy script as example:
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.WsdlTestSuite;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep
def prj = new WsdlProject(path_prj_file,null);
def tsuite = prj.getTestSuiteByName("TestSuiteName");
def tcase = tsuite.getTestCaseByName("TestCaseName");
def tstep = tcase.getTestStepByName("TestStep");
EDIT 2:
I download jython standalone version 2.5.3 and use soapui 5.0.0 and works for me:
hw.py
from com.eviware.soapui.tools import (SoapUITestCaseRunner)
from com.eviware.soapui.tools import (SoapUILoadTestRunner)
from com.eviware.soapui.tools import (SoapUIMockServiceRunner)
from com.eviware.soapui.impl.wsdl import (WsdlProject)
from com.eviware.soapui.impl.wsdl import (WsdlTestSuite)
from com.eviware.soapui.impl.wsdl.testcase import (WsdlTestCase)
from com.eviware.soapui.impl.wsdl.teststeps import (WsdlTestRequestStep)
import thread
class SoapUI2:
""" The main class of the library """
ROBOT_LIBRARY_SCOPE = 'TEST CASE'
ROBOT_LIBRARY_VERSION = '0.2'
def __init__(self):
self.__runner = None
self.__mockrunner = None
self._project_properties = []
self.__prj = WsdlProject('C:\soapui_project.xml', None)
self.__tsuite = self.__prj.getTestSuiteByName("myTestSuite")
self.__tcase = self.__tsuite.getTestCaseByName("myTestCase")
self.__tstep = self.__tcase.getTestStepByName("myTestStep")
t = self.__tstep.getPropertyValue("Value")
print "Works ok"
def soapui_project(self, prj):
""" Initialize the runner and set the project string """
self.__runner = SoapUITestCaseRunner()
self.__runner.setProjectFile(prj)
def soapui_multiproject(self, prj):
""" Initialize the runner and set the project string """
self.__runner = SoapUILoadTestRunner()
self.__runner.setProjectFile(prj)
def soapui_suite(self, s):
""" Set the suite string """
self.__runner.setTestSuite(s)
def soapui_case(self, c):
""" Set the test case string """
self.__runner.setTestCase(c)
def soapui_set_project_property(self, *properties):
""" Sets project properties for the current test run.
This assumes that you have already initialized the project via
the `SoapUI Project` keyword.
`properies` may contain multiple statements, and each must be specified as: key=value.
This is useful to data drive your existing SoapUI tests via property expansion.
For more information see: http://www.soapui.org/Scripting-Properties/property-expansion.html
Example:
| SoapUI Project | My Project |
| SoapUI Set Project Property | ServiceEndpoint=https://staging.company.com | # set a single property |
| SoapUI Set Project Property | CustomProperty=foo | AnotherProperty=bar | # or set multiple properties |
"""
for prop in properties:
if len(prop.split('=')) == 2:
self._project_properties.append(prop)
else:
logger.warn("Skipping property: '%s'. Properties must be specified as: key=value" % prop)
try:
self.__runner.setProjectProperties(self._project_properties)
except AttributeError:
logger.warn('No project set. Cannot set project properties.')
def soapui_set_multiproject_threads(self, t):
""" Sets number of threads to run in load test """
self.__runner.setThreadCount(long(t))
logger.info("Running with %s threads at once." % t)
def soapui_run(self):
""" Run the runner and report to Robot """
logger.info("Running with the following project properties set: %s" % self._project_properties)
if not self.__runner.run():
raise AssertionError('FAIL: failed to run')
if self.__runner == SoapUITestCaseRunner():
n = self.__runner.getFailedTests().size()
if n != 0:
raise AssertionError('FAIL: ' + str(n) + ' tests failed')
def soapui_start_mock_service(self, p, m):
""" Runs a mock service """
try:
self.__mockrunner = SoapUIMockServiceRunner()
self.__mockrunner.setProjectFile(p)
self.__mockrunner.setMockService(m)
self.__mockrunner.setBlock(False)
self.__mockrunner.run()
except Exception, e:
raise AssertionError('FAIL: Error running the mock service ' + m + '. Reason: ' + str(e))
def soapui_stop_mock_service(self):
""" Stops the mock service """
self.__mockrunner.stopAll()
def soapui_set_step_property(self, s, p,v):
testStep = self.__runner.testCase.getTestStepByName(s)
testStep.setPropertyValue(p,v)
if __name__ == "__main__":
SoapUI2().__init__()
cmd line execution:
java -classpath "jython-standalone-2.5.3.jar;C:\Programari\SoapUI-5.0.0\lib\*;C:\Programari\SoapUI-5.0.0\bin\*" org.python.util.jython hw.py
execution result:
2014-05-27 12:43:09,058 [main] WARN com.eviware.soapui.SoapUI - Could not find jfxrt.jar. Internal browser will be disabled.
12:43:09,589 WARN [SoapUI] Missing folder [C:\temp\ext] for external libraries
12:43:09,964 INFO [DefaultSoapUICore] initialized soapui-settings from [C:\Documents and Settings\aciffone\soapui-settings.xml]
12:43:09,995 INFO [HttpClientSupport$Helper] Initializing KeyStore
12:43:11,682 INFO [WsdlProject] Loaded project from [file:/C:/soapui_project.xml]
Works ok
Hope this helps,