You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by tz...@apache.org on 2020/03/26 14:27:38 UTC
[flink-statefun] branch release-2.0 updated: [FLINK-16783] Add
Python docker-compose based example
This is an automated email from the ASF dual-hosted git repository.
tzulitai pushed a commit to branch release-2.0
in repository https://gitbox.apache.org/repos/asf/flink-statefun.git
The following commit(s) were added to refs/heads/release-2.0 by this push:
new a558a01 [FLINK-16783] Add Python docker-compose based example
a558a01 is described below
commit a558a01849b6b0f61983745e54916b4a12346c80
Author: Igal Shilman <ig...@gmail.com>
AuthorDate: Wed Mar 25 22:12:37 2020 +0100
[FLINK-16783] Add Python docker-compose based example
This closes #74.
---
.../statefun-python-greeter/Dockerfile | 22 +++
.../statefun-python-greeter/README.md | 32 ++++
.../statefun-python-greeter/build-example.sh | 40 +++++
.../statefun-python-greeter/docker-compose.yml | 73 +++++++++
.../statefun-python-greeter/generator/Dockerfile | 30 ++++
.../generator/event-generator.py | 104 +++++++++++++
.../generator/messages_pb2.py | 172 +++++++++++++++++++++
.../statefun-python-greeter/greeter/Dockerfile | 34 ++++
.../statefun-python-greeter/greeter/greeter.py | 82 ++++++++++
.../statefun-python-greeter/greeter/messages.proto | 44 ++++++
.../greeter/messages_pb2.py | 172 +++++++++++++++++++++
.../greeter/requirements.txt | 22 +++
.../statefun-python-greeter/module.yaml | 54 +++++++
13 files changed, 881 insertions(+)
diff --git a/statefun-examples/statefun-python-greeter/Dockerfile b/statefun-examples/statefun-python-greeter/Dockerfile
new file mode 100644
index 0000000..9f3a803
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/Dockerfile
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 statefun
+
+RUN mkdir -p /opt/statefun/modules/greeter
+ADD module.yaml /opt/statefun/modules/greeter
+
+
diff --git a/statefun-examples/statefun-python-greeter/README.md b/statefun-examples/statefun-python-greeter/README.md
new file mode 100644
index 0000000..aee4826
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/README.md
@@ -0,0 +1,32 @@
+# The Greeter Example
+
+This is a simple example that runs a simple stateful function that accepts requests from a Kafka ingress,
+and then responds by sending greeting responses to a Kafka egress. It demonstrates the primitive building blocks
+of a Stateful Functions applications, such as ingresses, handling state in functions,
+and sending messages to egresses.
+
+
+## Building the example
+
+1) Make sure that you have built the Python distribution
+ To build the distribution
+ - `cd statefun-python-sdk/`
+ - `./build-distribution.sh`
+
+2) Run `./build-example.sh`
+
+## Running the example
+
+To run the example:
+
+```
+./build-example.sh
+docker-compose up -d
+```
+
+Then, to see the example in actions, see what comes out of the topic `greetings`:
+
+```
+docker-compose logs -f event-generator
+```
+
diff --git a/statefun-examples/statefun-python-greeter/build-example.sh b/statefun-examples/statefun-python-greeter/build-example.sh
new file mode 100755
index 0000000..9440c5d
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/build-example.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+# clean
+rm -f apache_flink_statefun-*-py3-none-any.whl
+rm -rf __pycache__
+
+# copy the whl distribution
+cp ../../statefun-python-sdk/dist/apache_flink_statefun-*-py3-none-any.whl greeter/apache_flink_statefun-snapshot-py3-none-any.whl 2>/dev/null
+rc=$?
+if [[ ${rc} -ne 0 ]]; then
+ echo "Failed copying the whl distribution, please build the Python distribution first."
+ echo "To build the distribution:"
+ echo " goto to statefun-python-sdk/"
+ echo " call ./build-distribution.sh"
+ exit 1;
+fi
+
+# build
+
+docker-compose build
+
+rm -f greeter/apache_flink_statefun-*-py3-none-any.whl
+
+echo "Done. To start the example run: docker-compose up"
+
diff --git a/statefun-examples/statefun-python-greeter/docker-compose.yml b/statefun-examples/statefun-python-greeter/docker-compose.yml
new file mode 100644
index 0000000..501e36b
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/docker-compose.yml
@@ -0,0 +1,73 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+version: "2.1"
+services:
+ zookeeper:
+ image: wurstmeister/zookeeper
+ ports:
+ - "2181:2181"
+ kafka-broker:
+ image: wurstmeister/kafka:2.12-2.0.1
+ ports:
+ - "9092:9092"
+ environment:
+ HOSTNAME_COMMAND: "route -n | awk '/UG[ \t]/{print $$2}'"
+ KAFKA_CREATE_TOPICS: "names:1:1,greetings:1:1"
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ depends_on:
+ - zookeeper
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ master:
+ build:
+ context: .
+ expose:
+ - "6123"
+ ports:
+ - "8081:8081"
+ environment:
+ - ROLE=master
+ - MASTER_HOST=master
+ volumes:
+ - ./checkpoint-dir:/checkpoint-dir
+ worker:
+ build:
+ context: .
+ expose:
+ - "6121"
+ - "6122"
+ depends_on:
+ - master
+ - kafka-broker
+ links:
+ - "master:master"
+ - "kafka-broker:kafka-broker"
+ environment:
+ - ROLE=worker
+ - MASTER_HOST=master
+ volumes:
+ - ./checkpoint-dir:/checkpoint-dir
+ python-worker:
+ build:
+ context: ./greeter
+ expose:
+ - "8000"
+ event-generator:
+ build:
+ context: generator
+ dockerfile: Dockerfile
+ depends_on:
+ - kafka-broker
diff --git a/statefun-examples/statefun-python-greeter/generator/Dockerfile b/statefun-examples/statefun-python-greeter/generator/Dockerfile
new file mode 100644
index 0000000..c3cf87c
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/generator/Dockerfile
@@ -0,0 +1,30 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 python:3.7-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+
+RUN pip install protobuf
+RUN pip install kafka-python
+
+COPY event-generator.py /app
+COPY messages_pb2.py /app
+
+CMD ["python", "/app/event-generator.py"]
+
+
diff --git a/statefun-examples/statefun-python-greeter/generator/event-generator.py b/statefun-examples/statefun-python-greeter/generator/event-generator.py
new file mode 100644
index 0000000..385ef7d
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/generator/event-generator.py
@@ -0,0 +1,104 @@
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 signal
+import sys
+import time
+import threading
+
+import random
+
+from kafka.errors import NoBrokersAvailable
+
+from messages_pb2 import GreetRequest, GreetResponse
+
+from kafka import KafkaProducer
+from kafka import KafkaConsumer
+
+KAFKA_BROKER = "kafka-broker:9092"
+NAMES = ["Jerry", "George", "Elaine", "Kramer", "Newman", "Frank"]
+
+
+def random_requests():
+ """Generate infinite sequence of random GreetRequests."""
+ while True:
+ request = GreetRequest()
+ request.name = random.choice(NAMES)
+ yield request
+
+
+def produce():
+ if len(sys.argv) == 2:
+ delay_seconds = int(sys.argv[1])
+ else:
+ delay_seconds = 1
+ producer = KafkaProducer(bootstrap_servers=[KAFKA_BROKER])
+ for request in random_requests():
+ key = request.name.encode('utf-8')
+ val = request.SerializeToString()
+ producer.send(topic='names', key=key, value=val)
+ producer.flush()
+ time.sleep(delay_seconds)
+
+
+def consume():
+ consumer = KafkaConsumer(
+ 'greetings',
+ bootstrap_servers=[KAFKA_BROKER],
+ auto_offset_reset='earliest',
+ group_id='event-gen')
+ for message in consumer:
+ response = GreetResponse()
+ response.ParseFromString(message.value)
+ print("%s:\t%s" % (response.name, response.greeting), flush=True)
+
+
+def handler(number, frame):
+ sys.exit(0)
+
+
+def safe_loop(fn):
+ while True:
+ try:
+ fn()
+ except SystemExit:
+ print("Good bye!")
+ return
+ except NoBrokersAvailable:
+ time.sleep(2)
+ continue
+ except Exception as e:
+ print(e)
+ return
+
+
+def main():
+ signal.signal(signal.SIGTERM, handler)
+
+ producer = threading.Thread(target=safe_loop, args=[produce])
+ producer.start()
+
+ consumer = threading.Thread(target=safe_loop, args=[consume])
+ consumer.start()
+
+ producer.join()
+ consumer.join()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/statefun-examples/statefun-python-greeter/generator/messages_pb2.py b/statefun-examples/statefun-python-greeter/generator/messages_pb2.py
new file mode 100644
index 0000000..bce4d24
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/generator/messages_pb2.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+################################################################################
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: messages.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='messages.proto',
+ package='example',
+ syntax='proto3',
+ serialized_options=None,
+ serialized_pb=_b('\n\x0emessages.proto\x12\x07\x65xample\"\x1c\n\x0cGreetRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"/\n\rGreetResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08greeting\x18\x02 \x01(\t\"\x19\n\tSeenCount\x12\x0c\n\x04seen\x18\x01 \x01(\x03\x62\x06proto3')
+)
+
+
+
+
+_GREETREQUEST = _descriptor.Descriptor(
+ name='GreetRequest',
+ full_name='example.GreetRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='example.GreetRequest.name', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=27,
+ serialized_end=55,
+)
+
+
+_GREETRESPONSE = _descriptor.Descriptor(
+ name='GreetResponse',
+ full_name='example.GreetResponse',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='example.GreetResponse.name', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='greeting', full_name='example.GreetResponse.greeting', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=57,
+ serialized_end=104,
+)
+
+
+_SEENCOUNT = _descriptor.Descriptor(
+ name='SeenCount',
+ full_name='example.SeenCount',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='seen', full_name='example.SeenCount.seen', index=0,
+ number=1, type=3, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=106,
+ serialized_end=131,
+)
+
+DESCRIPTOR.message_types_by_name['GreetRequest'] = _GREETREQUEST
+DESCRIPTOR.message_types_by_name['GreetResponse'] = _GREETRESPONSE
+DESCRIPTOR.message_types_by_name['SeenCount'] = _SEENCOUNT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GreetRequest = _reflection.GeneratedProtocolMessageType('GreetRequest', (_message.Message,), dict(
+ DESCRIPTOR = _GREETREQUEST,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.GreetRequest)
+ ))
+_sym_db.RegisterMessage(GreetRequest)
+
+GreetResponse = _reflection.GeneratedProtocolMessageType('GreetResponse', (_message.Message,), dict(
+ DESCRIPTOR = _GREETRESPONSE,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.GreetResponse)
+ ))
+_sym_db.RegisterMessage(GreetResponse)
+
+SeenCount = _reflection.GeneratedProtocolMessageType('SeenCount', (_message.Message,), dict(
+ DESCRIPTOR = _SEENCOUNT,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.SeenCount)
+ ))
+_sym_db.RegisterMessage(SeenCount)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/statefun-examples/statefun-python-greeter/greeter/Dockerfile b/statefun-examples/statefun-python-greeter/greeter/Dockerfile
new file mode 100644
index 0000000..d313fdd
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/greeter/Dockerfile
@@ -0,0 +1,34 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 python:3.7-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+
+COPY apache_flink_statefun-snapshot-py3-none-any.whl /app
+RUN pip install apache_flink_statefun-snapshot-py3-none-any.whl
+
+COPY requirements.txt /app
+RUN pip install -r requirements.txt
+
+COPY greeter.py /app
+COPY messages_pb2.py /app
+
+EXPOSE 8000
+
+CMD ["gunicorn", "-b", "0.0.0.0:8000", "-w 4", "greeter:app"]
+
diff --git a/statefun-examples/statefun-python-greeter/greeter/greeter.py b/statefun-examples/statefun-python-greeter/greeter/greeter.py
new file mode 100644
index 0000000..bc56a12
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/greeter/greeter.py
@@ -0,0 +1,82 @@
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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 messages_pb2 import SeenCount, GreetRequest, GreetResponse
+
+from statefun import StatefulFunctions
+from statefun import RequestReplyHandler
+from statefun import kafka_egress_record
+
+functions = StatefulFunctions()
+
+
+@functions.bind("example/greeter")
+def greet(context, greet_request: GreetRequest):
+ state = context.state('seen_count').unpack(SeenCount)
+ if not state:
+ state = SeenCount()
+ state.seen = 1
+ else:
+ state.seen += 1
+ context.state('seen_count').pack(state)
+
+ response = compute_greeting(greet_request.name, state.seen)
+
+ egress_message = kafka_egress_record(topic="greetings", key=greet_request.name, value=response)
+ context.pack_and_send_egress("example/greets", egress_message)
+
+
+def compute_greeting(name, seen):
+ """
+ Compute a personalized greeting, based on the number of times this @name had been seen before.
+ """
+ templates = ["", "Welcome %s", "Nice to see you again %s", "Third time is a charm %s"]
+ if seen < len(templates):
+ greeting = templates[seen] % name
+ else:
+ greeting = "Nice to see you at the %d-nth time %s!" % (seen, name)
+
+ response = GreetResponse()
+ response.name = name
+ response.greeting = greeting
+
+ return response
+
+
+handler = RequestReplyHandler(functions)
+
+#
+# Serve the endpoint
+#
+
+from flask import request
+from flask import make_response
+from flask import Flask
+
+app = Flask(__name__)
+
+
+@app.route('/statefun', methods=['POST'])
+def handle():
+ response_data = handler(request.data)
+ response = make_response(response_data)
+ response.headers.set('Content-Type', 'application/octet-stream')
+ return response
+
+
+if __name__ == "__main__":
+ app.run()
diff --git a/statefun-examples/statefun-python-greeter/greeter/messages.proto b/statefun-examples/statefun-python-greeter/greeter/messages.proto
new file mode 100644
index 0000000..3dc1a50
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/greeter/messages.proto
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+syntax = "proto3";
+
+// protoc *.proto --python_out=.
+
+package example;
+
+// External request sent by a user who wants to be greeted
+message GreetRequest {
+ // The name of the user to greet
+ string name = 1;
+}
+
+// A customized response sent to the user
+message GreetResponse {
+ // The name of the user being greeted
+ string name = 1;
+ // The users customized greeting
+ string greeting = 2;
+}
+
+// An internal message used to store state
+message SeenCount {
+ // The number of times a users has been seen so far
+ int64 seen = 1;
+}
+
+
diff --git a/statefun-examples/statefun-python-greeter/greeter/messages_pb2.py b/statefun-examples/statefun-python-greeter/greeter/messages_pb2.py
new file mode 100644
index 0000000..bce4d24
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/greeter/messages_pb2.py
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+################################################################################
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+################################################################################
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: messages.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='messages.proto',
+ package='example',
+ syntax='proto3',
+ serialized_options=None,
+ serialized_pb=_b('\n\x0emessages.proto\x12\x07\x65xample\"\x1c\n\x0cGreetRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"/\n\rGreetResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08greeting\x18\x02 \x01(\t\"\x19\n\tSeenCount\x12\x0c\n\x04seen\x18\x01 \x01(\x03\x62\x06proto3')
+)
+
+
+
+
+_GREETREQUEST = _descriptor.Descriptor(
+ name='GreetRequest',
+ full_name='example.GreetRequest',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='example.GreetRequest.name', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=27,
+ serialized_end=55,
+)
+
+
+_GREETRESPONSE = _descriptor.Descriptor(
+ name='GreetResponse',
+ full_name='example.GreetResponse',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='example.GreetResponse.name', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='greeting', full_name='example.GreetResponse.greeting', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=57,
+ serialized_end=104,
+)
+
+
+_SEENCOUNT = _descriptor.Descriptor(
+ name='SeenCount',
+ full_name='example.SeenCount',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='seen', full_name='example.SeenCount.seen', index=0,
+ number=1, type=3, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto3',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=106,
+ serialized_end=131,
+)
+
+DESCRIPTOR.message_types_by_name['GreetRequest'] = _GREETREQUEST
+DESCRIPTOR.message_types_by_name['GreetResponse'] = _GREETRESPONSE
+DESCRIPTOR.message_types_by_name['SeenCount'] = _SEENCOUNT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GreetRequest = _reflection.GeneratedProtocolMessageType('GreetRequest', (_message.Message,), dict(
+ DESCRIPTOR = _GREETREQUEST,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.GreetRequest)
+ ))
+_sym_db.RegisterMessage(GreetRequest)
+
+GreetResponse = _reflection.GeneratedProtocolMessageType('GreetResponse', (_message.Message,), dict(
+ DESCRIPTOR = _GREETRESPONSE,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.GreetResponse)
+ ))
+_sym_db.RegisterMessage(GreetResponse)
+
+SeenCount = _reflection.GeneratedProtocolMessageType('SeenCount', (_message.Message,), dict(
+ DESCRIPTOR = _SEENCOUNT,
+ __module__ = 'messages_pb2'
+ # @@protoc_insertion_point(class_scope:example.SeenCount)
+ ))
+_sym_db.RegisterMessage(SeenCount)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/statefun-examples/statefun-python-greeter/greeter/requirements.txt b/statefun-examples/statefun-python-greeter/greeter/requirements.txt
new file mode 100644
index 0000000..fcbc07c
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/greeter/requirements.txt
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apache-flink-statefun
+flask==1.1.1
+gunicorn==20.0.4
+
+
+
diff --git a/statefun-examples/statefun-python-greeter/module.yaml b/statefun-examples/statefun-python-greeter/module.yaml
new file mode 100644
index 0000000..146e947
--- /dev/null
+++ b/statefun-examples/statefun-python-greeter/module.yaml
@@ -0,0 +1,54 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+version: "1.0"
+module:
+ meta:
+ type: remote
+ spec:
+ functions:
+ - function:
+ meta:
+ kind: http
+ type: example/greeter
+ spec:
+ endpoint: http://python-worker:8000/statefun
+ states:
+ - seen_count
+ maxNumBatchRequests: 500
+ timeout: 2min
+ ingresses:
+ - ingress:
+ meta:
+ type: statefun.kafka.io/routable-protobuf-ingress
+ id: example/names
+ spec:
+ address: kafka-broker:9092
+ consumerGroupId: my-group-id
+ topics:
+ - topic: names
+ typeUrl: com.googleapis/example.GreetRequest
+ targets:
+ - example/greeter
+ egresses:
+ - egress:
+ meta:
+ type: statefun.kafka.io/generic-egress
+ id: example/greets
+ spec:
+ address: kafka-broker:9092
+ deliverySemantic:
+ type: exactly-once
+ transactionTimeoutMillis: 100000
+