30 Days of Go: Pointers and Structs
Learning Update
This post covers days 4, 5, and six of my 30 days of Golang journey. I was not able to cover a whole lot because sometimes life gets in the way. What I was able to cover was quite important though. It was the last step I needed before trying to create an application.
These are my notes on Go’s pointers and Structs.
Pointers
Go pointers allow you to pass references to values within a program. An example of this would be as follows.
package main
import "fmt"
// This is a regular function in Go
// It has a regular integer argument
func nonPointerFunc(val int) {
val = 0
}
// This function takes a pointer as an argument
// This is denoted by the *int, this means pval is a pointer to an integer value
func pointerFunc(pval *int) {
// The * infront of pval dereferences the pointer so that we can work with the
// value that the pointer references
*pval = 0
}
func main() {
// Initialize a value
x := 1
// Pass the value to nonPointerFunc
nonPointerFunc(x)
// After execution print the value of x and you will find it is 1
fmt.Println("Value after non pointer function: ", x)
// Pass a pointer to x into pointerFunc
// To get a pointer to a function use the syntax &value
// &x is the pointer for x
pointerFunc(&x)
// Print x after the execution and you will get a value of 0
fmt.Println("Value after pointer function:", x)
// To see the pointer itself you can print it out this way.
fmt.Println("Pointer ", &x)
}
To understand pointers better I watched this very helpful video by Junmin Lee. She explains Golang’s stack and heap to illustrate how pointers work. These are the takeaways I got based on my understanding:
- When you pass an argument into a Go function e.g calling a function from the main function. Go will create a copy of it and that copy is what will be used for computations within the called function. The variable that was passed in from main will remain unchanged.
- Passing a pointer of a value into a function will not create a copy of the value. The value referenced by the pointer is what will be modified by the function.
- Using pointers is more efficient because it does not create copies of variables. By using pointers we are giving up immutability for efficiency. To understand this better I will do more research and write a blog post on this.
Structs
Structs are typed collections of fields. Go uses structs to group data together to form records.
Creating a struct
type macbook struct {
model string
price int
}
// creating a variable with the struct
air = macbook{"M1 air 8gb", 1000}
pro = macbook{model: "M1 pro 32gb", price:2000}
// Fields that have been ommitted will be zero-valued
proMax = macbook{price: 3500}
// struct fields can be accessed with a dot
fmt.Println(pro.model)
// dots can also be used with a struct pointer, the pointer will be automatically
// dereferenced
// Note that structs are mutable so updating the pointer will also update the initial value
sp := &air
fmt.Println(sp.price)
To write more idiomatic Go constructor functions are used to create new structs.
func newMacbook(model string, price int) *macbook {
m := macbook{model: model, price: price}
m.price = 1000
return &m
}
- Returning the pointer of a local variable is allowed because it will survive the scope of the function. To use this constructor function we write this line of code.
- I need to read more on stack and heap allocation with this type of constructor function.
mini = newMacbook("M1 mini 8gb", 700)
fmt.Println(mini)
Methods
Go allows for methods on struct types. These methods are defined as follows:
type rect struct {
width, height int
}
// golang methods can have pointer recivers
func (r *rect) area() int {
return r.width * r.height
}
// golang methods can have value recivers
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 20, height: 10}
// Go automatically handles conversion between values and pointers for method calls.
rp := &r
fmt.Prinln("area: ", rp.area())
}
Pointer receiver types can be used to avoid copying on method calls. They also allow the method to mutate the receiving struct.
What next
I have gone through quite a few basics and now would like to learn by actually building something. The first thing I’m going to do is exercise one from the gophercises website. The exercise is to build a quiz game. The game reads questions and answers from a CSV and allows a user to answer them from the terminal.
I’ll be documenting building this over the next few days.
30 Days of Go: Day 2 and 3
How it’s going
This is my log update for my golang learnings for days two and three. I’ve been itching to actually start building something. I’m getting pretty close to that point. But before that I’d like to go through functions, methods and structs. Once that’s done I’ll start going a little in depth with building a few simple toy applications.
Day 2
I got quite a bit done on this day.
Data structures
Arrays
- Arrays in Go work like they do in a language like Java. All arrays need a type and size when they are declared.
- All values in an initialized array are zero-valued
var array [40]int
// Declare and initialize
array2 := [5]int{1, 3, 4, 5, 6}
// Access an index in the array
fmt.Println(array[20])
// add something to an index in the array
array[10] = 300
// get the size of the array
fmt.Println(len(array))
- Golang has 2D arrays. These are composed in the following way
var twoD [4][9]int
for i := 0; i < 4; i++ {
for j := 0; j < 9; j++ {
twoD[i][j] = i +
}
}
Slices
- A slice is a data structure that works like an array with a variable size. It is like an arraylist in Java or a list in python (with the caveat that all items in the slice have to be of the same type).
- To initialize a slice Go has a builtin function
make
// Create a slice pass the type and initial size to make
sl := make([]string, 3)
// Declaring and initializing a slice
t := []string{"ab", "bc", "cd"}
// Access an item in a slice
fmt.Println(sl[2])
// set an index in a slice
sl[1] = "Hello"
- To add more items in a slice use the builtin function
append
. The append function takes a slice and a value that you would like to add to that slice and returns a new slice
sl = append(sl, "new")
- Slices can be copied using the builtin
copy
function
// Create a new slice with the length of the slice you want to copy
c := make([]string, len(sl))
// copies sl into c
copy(c, sl)
- slices support slicing, like python lists
// creates a nes slice from index 1 to 2
newSlice := sl[1:3]
// creates slice from index 0 to 3 (excluding 4)
newSlice = sl[:4]
// slice from index 2 to the last one
newSlice = sl[2:]
- slices support multidimensional data structures. The length of the inner slice can be variable.
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
// result = [[0] [1 2] [2 3 4]]
Maps
- These are key/value associative data structures like python dicts
- Use
make
to create an empty map
m := make(map[string]int)
// declare and initialize a map
m2 := map[string]int{"foo": 4, "bar": 40}
// set key/value pairs
m["one"] = 2
m["two"] = 3
// get a value with a key
v := m["one"]
// get number of key/value pairs
fmt.Println(len(m))
// To delete a key value pair use builtin function
delete(m, "two")
- A second value is returned when getting from a map,
value, prs := map[key]
. prs will be true if the key is in the map and false if it isn’t
_, prs := m["two"]
- Blank identifier
_
is used when a value is not needed. This is necessary because Go requires that you use every variable that has been declared.
Range
- In Go we can use a range to iterate over data structures like arrays, slices, maps and strings
// array, slices
for index, value := range array {
fmt.Println(value)
}
//map
for key, value := range exMap {
fmt.Printf("%s -> %s\n", key, value)
}
// iterate over map keys only
for key := range exMap {
fmt.Println(key)
}
// iterate over strings
for index, ru := range "Golang" {
fmt.Println(ru)
}
Day 3
I didn’t get through as much today but still interesting things were coverd.
Functions
- Functions can have multiple return values. These functions are usually used to return the result and error values from a function
- Variadic functions can be called with any number of arguments
- Functions are defined in the following way:
// function structure
// function name(arguments) return type {}
func sayHello(name string) string {
return "Hello " + name
}
// call the function sayHello("Kacha")
// function with multiple return values
func names() (string, string) {
return "Scott", "Ramona"
}
// variadic function
func add(nums ...int) {
total := 0
for _, num := range nums {
total += num
}
return total
}
- Go also supports anonymous functions which can form closures. I do not quite understand closures quite yet so I’ll have that in a different blog post.
…
I want to try and blog on each day but that might not be possible so I will blog my progress at least every two or three days.
30 Days of Go: Day 1
Introduction
I have decided to take part in a challenge to learn Golang in the next 30 days. I found a Go hackathon and thought this is the perfect opportunity because I have wanted to learn it for a while now. I have decided to split this learning up in a couple of ways. The idea is to build an application for the hackathon. My current app idea is a citizen science API. This API will allow users to upload photos of animals such as birds or insects.
I have split my learnings into the following categories:
- Weeks 1 & 2: Learn the syntax and basics of Golang and do the gophercises
- Week 3 & 4: Build out the API
Golang syntax
This information is referenced Golang documentation and especially Go by example.
Variables
- Golang’s variables are explicitly declared like C#, Java
- Golang allows multiple variables to be declared in one line
- If the type has not been specified go will infer the types from initialized values
var a = "Hello world"
var age int = 8
// When the type has been declared it is used for both variables
var name, game string = "Scott Pilgrim", "vs The world"
// This is allowed because go will infer the types from initialization
var name, age = "Knives Chau", 18
- Variables that are declared but not initialized will be zero-valued
- All declared but not initialized variables must have a type
var one int
// one = 0
var decision bool
// decision = false
var word string
// word = ''
:=
is shorthand for declaring and initializing a variable e.g
num := 50
Constants
- Constants in Go work the same as in other languages. A const can be a string, character, bool, or numerical type.
const test string = "Steven Stills"
- Numerical constants perform arithmetic with arbitrary precision
- A numerical constant has no type until it is given one from a function or is cast to a type
Flow Control
Loops
- Go only has for loops and they can be initialized in the following ways:
i := 0
for i <= 4 {
fmt.Println(i)
i = i + 1
}
for j := 4; j <=8; j++ {
fmt.Println(j)
}
- infinite loops have no conditions and can be stopped with a break statement
for {
fmt.Println("The infinite sadness")
break
}
- the
continue
keyword can be used to go to the next iteration of a loop
for i :=3; i <= 33; i++ {
if i % 3 == 0 {
continue
}
fmt.Println("Love evans")
}
If else
- If else blocks are like most other programming languages
- Parenthesis are not needed in the if statement but braces are
if x == 3 {
fmt.Println("Young Neil")
} else if x > 5 {
fmt.Println("Neil")
} else {
fmt.Println("Meh")
}
- Go’s if block can have a statement before the condition. Any variable declared in the statement can be used in all the branches of the if block.
if x := 4; x < 0 {
fmt.Println(x)
} else if x > 1 {
fmt.Println("True")
} else {
fmt.Println("Do something else")
}
- Go has no ternary if statements like javascript
x == 2? "Yes": "No"
Switch statements
- A switch statement in Go is like other programming languages
- The default case is optional
- Cases can have multiple expressions
- a switch can have no expressions which will make it function like an if statement
switch i {
case 1:
fmt.Println("One")
case 2:
fmt.Println("Two")
case 3, 4, 5:
fmt.Println("Others")
default:
fmt.Println("Default is optional")
}
// Swtich with no condition
t := 5
switch {
case t < 4:
fmt.Println("Less than 4")
default:
fmt.Println("Else")
}
- A type switch compares types instead of values
- Use this switch to discover an interface’s type
- Using
.(type)
does not work outside a type switch
myType := func(i interface{}){
switch t := i.(type){
case bool:
fmt.Println("Im boolean")
case int:
fmt.Println("Im integer")
default:
fmt.Println("Im anything else")
}
}
This was a fruitful first day and there’s more to come.
Creating a todo web app in < 112 lines of code with Hasura and Python
Introduction
I created this application for the Hashnode Hasura hackathon. I had some trouble thinking of an application to create. I got over my indecision by reading this post. A todo app is a valid application so that is what I made.
Built with
- Hasura
- Python
- Streamlit
Setting up the backend
The backend of the application is developed with Hasura and Postgres. Doing this is really easy by following Hasura’s official documentation at this link here. It is really easy to follow along but let’s summarize the steps we can take to get our app up and running.
Hasura setup
Create a Hasura account and create a new project
Once the project has been created launch the project console. On the console click on the option to create a Heroku database.
Create the Users table in the launched Hasura console here
Create the Todos table in the Hasura console here
Create the foreign key relationships between the user and the todos here
Connecting our app to Hasura
Once we have our Hasura backend set up we can connect our Streamlit app to it. To do so we will need a GraphQL library to allow us to make GraphQL requests from python. Install the following libraries.
gql: Allows us to create GraphQL queries and mutations and requests from an API
aiohttp: This allows us to create asynchronous methods
dotenv: Allow us to read env files
pip install gql
pip install aiohttp
Once installed create a main.py file and add the following code:
import streamlit as st
import os
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from dotenv import load_dotenv
load_dotenv()
headers = {'x-hasura-admin-secret': os.environ["HASURA_ADMIN_SECRET"] }
transport = AIOHTTPTransport(url="https://curious-sailfish-33.hasura.app/v1/graphql", headers=headers)
client = Client(transport=transport, fetch_schema_from_transport=True)
First import all the required modules from the libraries we have installed.
Create headers that will be used in all the requests we make. Use a Hasura admin secret that you can get from your Hasura dashboard.
We will use an async transport protocol in our client to interact with our GraphQL endpoint.
CRUD operations
In our main.py we will create four methods to interact with our todos api.
Read Todos
def get_todos():
query = gql(
"""
query getTodos {
todos {
id
title
is_completed
}
}
"""
)
result = client.execute(query)
return result["todos"]
# Save all todos into our streamlit session for use in the application
if 'todos' not in st.session_state:
st.session_state['todos'] = get_todos()
Create a query to get all todos from our database. Save them to our local state to be used in the Streamlit application.
Create Todos
def create_todo(todo):
query = gql(
"""
mutation createTodo($title: String!, $user_id: String!) {
insert_todos_one(object: {title: $title, user_id: $user_id }){
id
title
is_completed
}
}
"""
)
params = {"title": todo, "user_id": "1"}
result = client.execute(query, variable_values=params)
todo = result['insert_todos_one']
st.session_state.todos.append(todo)
return result
Create a todo using a todo mutation and save the created todo into our session variable.
Update Todos
def update_todo(todo):
query = gql(
"""
mutation updateTodo($id: Int!, $is_completed: Boolean!){
update_todos(where: {id: {_eq: $id}}, _set: {is_completed: $is_completed}){
affected_rows
}
}
"""
)
params = {"id": todo["id"], "is_completed": not todo["is_completed"]}
result = client.execute(query, variable_values=params)
return result
Update a todo using an update mutation.
Delete Todos
def delete_todo(todo):
query = gql(
"""
mutation deleteTodo($id: Int!){
delete_todos(where: {id: {_eq: $id}}){
affected_rows
}
}
"""
)
params = {"id": todo["id"]}
result = client.execute(query, variable_values=params)
st.session_state.todos.remove(todo)
return result
Finally a method to delete a todo and delete it from our local state as well.
Setting up the frontend
To set up the web application frontend I used the streamlit library . This library turns python scripts into a web application.
Let’s set up the web front end:
st.title("My todos")
with st.form(key="my_form", clear_on_submit=True):
new_todo = st.text_input("Create a todo", value="")
submit = st.form_submit_button("Create")
if submit:
create_todo(new_todo)
todos = st.session_state.todos
not_done = [todo for todo in todos if not todo["is_completed"]]
completed = [todo for todo in todos if todo["is_completed"]]
st.subheader("Not Completed")
for todo in not_done:
col1, col2 = st.columns([3, 1])
col1.checkbox(todo["title"], key=todo["id"])
col2.button("delete", key=todo["id"], on_click=delete_todo, args=(todo, ))
st.subheader("Completed")
for todo in completed:
st.checkbox(todo["title"], key=todo["id"], value=todo["is_completed"], on_change=update_todo, kwargs={"todo": todo})
Let us go through these lines of code:
st.title("My todos")
This creates a heading on the web page. We then create a form that will allow a user to input a new todo. To do this we use Streamlit’s form component.
# Clear on submit clears the input once a user
with st.form(key="my_form", clear_on_submit=True)
new_todo = st.text_input("Create a todo", value="")
submit = st.form_submit_button("Create")
# when a user submits a todo we call the function create todo
if submit:
create_todo(new_todo)
Streamlit allows for state management between each run of the web script. We will save all the todos that we call from our Hasura backend.
todos = st.session_state.todos
# split the todos into done and incomplete
not_done = [todo for todo in todos if not todo["is_completed"]]
completed = [todo for todo in todos if todo["is_completed"]]
The final part to the UI is displaying the todos.
st.subheader("Not Completed")
for todo in not_done:
# Create a grid with streamlit
col1, col2 = st.columns([3, 1])
col1.checkbox(todo["title"], key=todo["id"])
# A delete button calls the delete_todo method when clicked
col2.button("delete", key=todo["id"], on_click=delete_todo, args=(todo, ))
st.subheader("Completed")
for todo in completed:
st.checkbox(todo["title"], key=todo["id"], value=todo["is_completed"], on_change=update_todo, kwargs={"todo": todo})
Putting it all together
Adding all the code snippets for the project:
import streamlit as st
import os
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
from dotenv import load_dotenv
load_dotenv()
def get_gql_client(extra_headers=None):
headers = {'x-hasura-admin-secret': os.environ["HASURA_ADMIN_SECRET"] }
if extra_headers:
headers += extra_headers
transport = AIOHTTPTransport(url="https://curious-sailfish-33.hasura.app/v1/graphql", headers=headers)
client = Client(transport=transport, fetch_schema_from_transport=True)
return client
headers = {'x-hasura-admin-secret': os.environ["HASURA_ADMIN_SECRET"] }
transport = AIOHTTPTransport(url="https://curious-sailfish-33.hasura.app/v1/graphql", headers=headers)
client = Client(transport=transport, fetch_schema_from_transport=True)
def get_todos():
query = gql(
"""
query getTodos {
todos {
id
title
is_completed
}
}
"""
)
result = client.execute(query)
return result["todos"]
if 'todos' not in st.session_state:
st.session_state['todos'] = get_todos()
def create_todo(todo):
query = gql(
"""
mutation createTodo($title: String!, $user_id: String!) {
insert_todos_one(object: {title: $title, user_id: $user_id }){
id
title
is_completed
}
}
"""
)
params = {"title": todo, "user_id": "1"}
result = client.execute(query, variable_values=params)
todo = result['insert_todos_one']
st.session_state.todos.append(todo)
return result
def update_todo(todo):
query = gql(
"""
mutation updateTodo($id: Int!, $is_completed: Boolean!){
update_todos(where: {id: {_eq: $id}}, _set: {is_completed: $is_completed}){
affected_rows
}
}
"""
)
params = {"id": todo["id"], "is_completed": not todo["is_completed"]}
result = client.execute(query, variable_values=params)
return result
def delete_todo(todo):
query = gql(
"""
mutation deleteTodo($id: Int!){
delete_todos(where: {id: {_eq: $id}}){
affected_rows
}
}
"""
)
params = {"id": todo["id"]}
result = client.execute(query, variable_values=params)
st.session_state.todos.remove(todo)
return result
st.title("My todos")
with st.form(key='my_form', clear_on_submit=True):
new_todo = st.text_input("Create a todo", value="", key="d")
submit = st.form_submit_button("Create")
if submit:
create_todo(new_todo)
todos = st.session_state.todos
not_done = [todo for todo in todos if not todo["is_completed"]]
completed = [todo for todo in todos if todo["is_completed"]]
st.subheader("Not Completed")
for todo in not_done:
col1, col2 = st.columns([3, 1])
col1.checkbox(todo["title"],key=todo["id"], value=todo["is_completed"], on_change=update_todo, args=todo)
col2.button("Delete", key=todo["id"], on_click=delete_todo, args=(todo, ))
st.subheader("Completed")
for todo in completed:
st.checkbox(todo["title"],key=todo["id"], value=todo["is_completed"], on_change=update_todo, kwargs={"todo": todo})
Deploy the application
To deploy our application create a requirements.txt file in our folder and add the following:
aiohttp
streamlit
python-dotenv
gql
We then push this repo to Github.
Sign up for an account on Streamlit and link it to your GitHub account. Create a new application and select the repository that was pushed. Streamlit will allow you to pick the branch and the file that the application will run from.
Click on Advanced settings to add our environment variables. This is where we will add the Hasura admin secret.
Once saved hit deploy and viola your application is ready to be used and shared.
What I learned
I learned quite a bit during this hackathon, it was my first time using Hasura. The following are a few things that I learned:
Creating a Hasura GraphQL backend
Connecting a Hasura API to python
Local state in a python Streamlit application
Auth0 for authentication purposes
Things I struggled with
The main thing I struggled with was getting Hasura’s authentication with Auth0 to play well with Streamlit. This was more a problem with me trying to fit a triangle peg in a square hole. Streamlit is not a conventional web framework so the concept of a redirect URL does not work with it. I tried to create an API backend authentication using a flask JWT API tutorial here.
I was able to get the sign-up route working fine but logging in was an issue because each JWT I generated was invalid. I’m sure there is something I missed in the documentation but for the life of me, I could not get it to work. I will revisit this and also push the code to my Github page.
Try it out
Find the relevant links below:
Creating a Whatsapp Chat analyser web app with Symbl.ai and python in less than 100 lines of code
This application will take an .txt export of a Whatsapp chat. The application will then run conversational analysis on the .txt file. Symbl.ai will then extract information like follow ups, topics and action items.
The application requires the following libraries:
- streamlit: This library converts python scripts into web applications.
- symbl.ai: This is a conversational A.I library that can get insights from audio and text.
Starting the Streamlit app
Streamlit is a great library I found that can convert python scripts into a web application.
Lets create an application that will accept a whatsapp txt chat file and parse the chat data.
import streamlit as st
chat_file = st.file_uploader("Select chat file")
if chat_file:
conversation_request = handle_uploaded_file(chat_file)
To handle the file and extract the messages in a format that symbl will understand we need to create a function to do it.
def handle_uploaded_file(file):
chat = file.read().decode("utf-8")
# Split each line from the file into a list and strip all whitespace
lines = chat.splitlines()
lines = [line.strip() for line in lines]
messages = []
cleaned_lines = []
# If line does not start with a Whatsapp date format [m/d/y, h:m:s am] then append it to the previous line.
for x in range(len(lines)):
if lines[x].find("[") == -1:
cleaned_lines[-1] = f"{cleaned_lines[-1]} {lines[x]}"
continue
cleaned_lines.append(lines[x])
# pares the time, name and message from a line
for line in cleaned_lines:
st_dt, en_dt = line.find("["), line.find("]")
time = line[st_dt:en_dt].replace("[", "")
name = line[en_dt:].split(":")[0].replace("]", "")
st_m = line[en_dt:].find(":")
message = line[en_dt:][st_m:].replace(":", "")
# Create messages in the format Symbl requires
messages.append(
{
"duration": {"startTime": arrow.get(time, 'M/D/YY, H:mm:ss A').format(), "endTime": arrow.get(time, 'M/D/YY, H:mm:ss A').format()},
"payload": {"content": message, "contentType": "text/plain"},
"from": {"name": name, "userId": name},
}
)
return {
"name": "Chat",
"confidenceThreshold": 0.6,
"detectPhrases": True,
"messages": messages,
}
Once we have the messages from the handle request file we can analyse it with Symbl.ai
import symbl
...
conversation_object = symbl.Text.process(payload=conversation_request)
Displaying the results
Add some display components using streamlit.
with st.expander("Topics"):
with st.spinner("Getting topics"):
topics = symbl.Conversations.get_topics(conversation_id=conversation_id)
for topic in topics.topics:
st.write(f"- {topic.text}")
with st.expander("Action Points"):
with st.spinner("Getting action items"):
action_items = symbl.Conversations.get_action_items(conversation_id=conversation_id)
for item in action_items.action_items:
st.write(f"- {item.text}")
with st.expander("Questions"):
questions = symbl.Conversations.get_questions(conversation_id=conversation_id)
for question in questions.questions:
st.write(f"- {question.text}")
with st.expander("Follow ups"):
follow_ups = symbl.Conversations.get_follow_ups(conversation_id=conversation_id)
for follow_up in follow_ups.follow_ups:
st.write(f"- {follow_up.text}")
The final code file
import streamlit as st
import arrow
import symbl
def handle_uploaded_file(file):
chat = file.read().decode("utf-8")
lines = chat.splitlines()
lines = [l.strip() for l in lines]
messages = []
cleaned_lines = []
for x in range(len(lines)):
if lines[x].find("[") == -1:
cleaned_lines[-1] = f"{cleaned_lines[-1]} {lines[x]}"
continue
cleaned_lines.append(lines[x])
for line in cleaned_lines:
st_dt, en_dt = line.find("["), line.find("]")
time = line[st_dt:en_dt].replace("[", "")
name = line[en_dt:].split(":")[0].replace("]", "")
st_m = line[en_dt:].find(":")
message = line[en_dt:][st_m:].replace(":", "")
messages.append(
{
"duration": {"startTime": arrow.get(time, 'M/D/YY, H:mm:ss A').format(), "endTime": arrow.get(time, 'M/D/YY, H:mm:ss A').format()},
"payload": {"content": message, "contentType": "text/plain"},
"from": {"name": name, "userId": name},
}
)
return {
"name": "Chat",
"confidenceThreshold": 0.6,
"detectPhrases": True,
"messages": messages,
}
chat_file = st.file_uploader("Select chat file")
if chat_file:
conversation_request = handle_uploaded_file(chat_file)
with st.spinner("Analysing Text"):
conversation_object = symbl.Text.process(payload=conversation_request)
conversation_id = conversation_object.get_conversation_id()
st.write(conversation_id)
with st.expander("Topics"):
with st.spinner("Getting topics"):
topics = symbl.Conversations.get_topics(conversation_id=conversation_id)
for topic in topics.topics:
st.write(f"- {topic.text}")
with st.expander("Action Points"):
with st.spinner("Getting action items"):
action_items = symbl.Conversations.get_action_items(conversation_id=conversation_id)
for item in action_items.action_items:
st.write(f"- {item.text}")
with st.expander("Questions"):
questions = symbl.Conversations.get_questions(conversation_id=conversation_id)
for question in questions.questions:
st.write(f"- {question.text}")
with st.expander("Follow ups"):
follow_ups = symbl.Conversations.get_follow_ups(conversation_id=conversation_id)
for follow_up in follow_ups.follow_ups:
st.write(f"- {follow_up.text}")
Screenshots of our final application
What I learned in February
In my quest to catalog my developer journey here are a few things I have learned this month.
React/Javascript
Local development of React applications with an express Api can have CORS errors. To solve this React has an option to serve the frontend on the same port as the backend. This is the proxy option that can be set in the React application’s package.json file.
To do this open your react package.json file and add the following option:
"proxy": "http://localhost:<EXPRESS PORT>"
To read more on this read the React documentation here.
Python
Running a python module or a script has a subtle difference: -m. To run a python module add the “-m” tag.
So if you have a module like this:
hello/
__init__.py
__main__.py
somescript.py
You can run it like this:
python -m <module-name>
python -m hello
As a note the module-name needs to be the name of the module not a string.
Django
Using Sass with Django.
Install the following libraries
pip install lib sass django-compressor django-sass-processor
Add the Django sass processor to your installed apps in your Django settings. Whilst in the settings add the following static files settings.
INSTALLED_APPS = [
...,
'sass_processor',
...
]
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'sass_processor.finders.CssFinder',
]
SASS_PROCESSOR_ROOT = os.path.join(BASE_DIR, 'static')
Pick your UI framework e.g. UIKit, Bootstrap, or Materialize. Copy the sass folder into your static files folder.
Create a main.scss file in the static files folder. This file is the primary way that the sass processor will interact with the sass framework.
In the base template add the following sass tags:
<link href="{% sass_src 'main.scss' %}" rel="stylesheet" type="text/css">
Django Deployment
Deploying an app to the Digital Ocean app platform with static files on digital ocean spaces. This will be a separate blog post.