Hello my friend,
First of all, Happy New Year! We hope that you had a great festive time with your beloved ones, families and friends. That’s the one of the most important part of our lives and, in our opinion, spending some time off the grid impacts our mental well-being positively and gives us energy to move forward and achieve new heights in professional and business areas.
Talking about the topic of today blog post, we thought it will be useful to show you a concept, which is Go (Golang) specific, as there is no such a need in Python. This concept is called “interfaces”, and it is extremely helpful when you work with external data, which you will face working with external data source, e.g. retrieving data from APIs with JSON/XML encoding.
Disclaimer, we talk about interfaces only in the context of the data types in Go (Golang), as it is also used for class composition (object-oriented programming), so we put it aside for now. We may get back to it later in our blog series.
Automation and AI?
If you follow latest trends, you see that AI in various forms, whether this is agentic AI, LLMs, etc are coming in our world. Network and IT infrastructure automation isn’t excluded here; AI will be used in automation. However, in order to be able to use AI properly, you shall be able first to automate IT infrastructure and networks without it.
We offer the following training programs in network automation for you:
- Zero-to-Hero Network Automation Training
- High-scale automation with Nornir
- Ansible Automation Orchestration with Ansble Tower / AWX
- Expert-level training: Closed-loop Automation and Next-generation Monitoring
During these trainings you will learn the following topics:
- Success and failure strategies to build the automation tools.
- Principles of software developments and the most useful and convenient tools.
- Data encoding (free-text, XML, JSON, YAML, Protobuf).
- Model-driven network automation with YANG, NETCONF, RESTCONF, GNMI.
- Full configuration templating with Jinja2 based on the source of truth (NetBox).
- Best programming languages (Python, Bash) for developing automation
- The most rock-solid and functional tools for configuration management (Ansible) and Python-based automation frameworks (Nornir).
- Network automation infrastructure (Linux, Linux networking, KVM, Docker).
- Orchestration of automation workflows with AWX and its integration with NetBox, GitHub, as well as custom execution environments for better scalability.
- Collection network data via SNMP and streaming telemetry with Prometheus
- Building API gateways with Python leveraging Fast API
- Integration of alerting with Slack and your own APIs
- … and many more
Moreover, we put all mentions technologies in the context of real use cases, which our team has solved and are solving in various projects in the service providers, enterprise and data centre networks and systems across the Europe and USA. That gives you opportunity to ask questions to understand the solutions in-depth and have discussions about your own projects. And on top of that, each technology is provided with online demos and labs to master your skills thoroughly. Such a mixture creates a unique learning environment, which all students value so much. Join us and unleash your potential.
Start your automation training today.
What Are We Going To Talk Today?
In today’s blog post we are going to discuss:
- What is the interface in context of data type in Go (Golang)?
- Why it is not applicable to Python?
- When to use it and when not?
- How to use it?
Explanation
Earlier we’ve mentioned that interfaces in context of data types in Go (Golang) is helpful when you deal with external data sources, such as retrieving data via APIs with JSON/XML.
The recommended way in Go (Golang) and in Python, when you deal with APIs is to use structs and data classes respectively, so that you have a clear understanding of:
- What keys you expect to have?
- What are their data types?
The high-level workflow when you deal with APIs is:
- You make API request (say GET – link)
- You receive the response
- You parse response in struct in Go (Golang) or in data class in Python.
- You use created struct / data class within your application logic further.
However, often you have to to deal with external APIs, which are not under your control. And from our experience, in many cases you are not aware about their schemas, meaning which keys exist, what data types are associated with them. Keys may also be added or removed during the lifetime of APIs without prior notice. Despite of that, you are still interested to receive all the possible keys with their original data types.
In Python it is straightforward. When we talked about dictionaries and maps (link), you have seen that Python dynamically pick up data types of keys inside dictionary. You may perform explicit type casting, but it is not needed unless you really need some conversions (e.g., strings to integers, etc).
Go (Golang) is strict typed programming language, meaning that you define upfront which data type is used for keys and what is used for their values, meaning all keys and values must be of these data types. E.g.:
1 var data map[string]string
will create a map/dictionary with keys and values being of string data types.
This is where interfaces for data type come to stage.
They allow to add some dynamics to data types in Go (Golang). The syntax to define such variables would be:
1 var data map[interface{}]interface{}
which means that both keys and interfaces can be of any data type (string, integer, float, boolean, etc). Let’s reflect on that for a bit:
- On the one hand, you would think that this is much more convenient compared to strict typing and you would be right.
- On the other hand, you pay price in terms of performance as Go (Golang) would need to use additional internal logic to detect data type (and as a primary purpose of using Go (Golang) instead of Python is performance, you wouldn’t want to sacrifice it. Additionally, the danger here is that you start relying on keys in map/dictionary in your business logic and at some point key suddenly change its data type or even completely removed, which will lead for your application to crash in some unpredictable patterns. Using defensive coding patterns, you would normally do data processing when you populate your struct, so you can make failures more predictable with clear error messages if something with your input data isn’t right.
That brings us to a question, whether you shall or shall not use interfaces in Go (Golang) for data types. From my perspective, the answer would be it depends. If performance isn’t a thing or you are doing exploration of APIs, interfaces could be a good option. Otherwise, I go with structs.
Interfaces in context of class composition is completely different topic, which possibly will be covered in future, after we cover main IT and network automation topics
Examples
To showcase operation of interface data type in Go (Golang) as well as why it is not needed in Python, we’ll take as a basis the inventory we’ve used earlier in blog post about maps and dictionaries and we’ll add there more data types:
- It already has strings
- we’ll add integers, floats and boolean
Then we’ll loop over all the variables to print their data types to ensure they are detected correctly.
Python
Generally, the Python code is very similar to what we had in dictionaries/maps.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 $ cat main.py
"""From Python to Go: Python: 009 - Interfaces data type"""
# Variables
inventory = [
{
"name": "leaf-01",
"os": "cisco-nxos",
"ip": "192.168.1.1",
"port": 22,
"latitude": 51.5120898,
"longitude": -0.0030987,
"active": True,
}, {
"name": "leaf-02",
"os": "arista-eos",
"ip": "192.168.1.2",
"port": 830,
"latitude": 51.5120427,
"longitude": 0.0044585,
"active": True,
}, {
"name": "spine-01",
"ip": "192.168.1.1",
"port": 22,
"latitude": 51.5112179,
"longitude": -0.0048555,
"active": False,
},
]
# Execution
if __name__ == "__main__":
# Print the entire map and data type
print(inventory, type(inventory))
# Print data types for each element
for item in inventory:
print(item, type(item))
for key, value in item.items():
print(key, value, type(value))
If you follow our blog series, by this time you shall be already comfortable with this snippet:
- First of all, in the variable “inventory” we add, as mentioned in the scenario description, a few new keys with different data types.
- Then we print the entire inventory and its data types using “type()” function.
- Afterwards, we loop through dictionaries and print their data types.
- Finally we print each key, its value and the data type of the value
Let’s see output of this Python script execution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 $ python main.py
[{'name': 'leaf-01', 'os': 'cisco-nxos', 'ip': '192.168.1.1', 'port': 22, 'latitude': 51.5120898, 'longitude': -0.0030987, 'active': True}, {'name': 'leaf-02', 'os': 'arista-eos', 'ip': '192.168.1.2', 'port': 830, 'latitude': 51.5120427, 'longitude': 0.0044585, 'active': True}, {'name': 'spine-01', 'ip': '192.168.1.1', 'port': 22, 'latitude': 51.5112179, 'longitude': -0.0048555, 'active': False}] <class 'list'>
{'name': 'leaf-01', 'os': 'cisco-nxos', 'ip': '192.168.1.1', 'port': 22, 'latitude': 51.5120898, 'longitude': -0.0030987, 'active': True} <class 'dict'>
name leaf-01 <class 'str'>
os cisco-nxos <class 'str'>
ip 192.168.1.1 <class 'str'>
port 22 <class 'int'>
latitude 51.5120898 <class 'float'>
longitude -0.0030987 <class 'float'>
active True <class 'bool'>
{'name': 'leaf-02', 'os': 'arista-eos', 'ip': '192.168.1.2', 'port': 830, 'latitude': 51.5120427, 'longitude': 0.0044585, 'active': True} <class 'dict'>
name leaf-02 <class 'str'>
os arista-eos <class 'str'>
ip 192.168.1.2 <class 'str'>
port 830 <class 'int'>
latitude 51.5120427 <class 'float'>
longitude 0.0044585 <class 'float'>
active True <class 'bool'>
{'name': 'spine-01', 'ip': '192.168.1.1', 'port': 22, 'latitude': 51.5112179, 'longitude': -0.0048555, 'active': False} <class 'dict'>
name spine-01 <class 'str'>
ip 192.168.1.1 <class 'str'>
port 22 <class 'int'>
latitude 51.5112179 <class 'float'>
longitude -0.0048555 <class 'float'>
active False <class 'bool'>
Interesting observation is that when you print the data type, you always see keyword “class”. This is because in Python everything is a class. But if you put the “class” aside, you see the actual data type: string, integer, float or boolean correctly identified.
Go (Golang)
Let’s now create the same code in Go (Golang):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 $ cat main.go
/* From Python to Go: Go (Golang): 009 - Interfaces data type */
package main
// Imports
import (
"fmt"
)
// Types
type Inventory []map[interface{}]interface{}
// Main
func main() {
// Define inventory
inventory := Inventory{
{
"name": "leaf-01",
"os": "cisco-nxos",
"ip": "192.168.1.1",
"port": 22,
"latitude": 51.5120898,
"longitude": -0.0030987,
"active": true,
}, {
"name": "leaf-02",
"os": "arista-eos",
"ip": "192.168.1.2",
"port": 830,
"latitude": 51.5120427,
"longitude": -0.0044585,
"active": true,
}, {
"name": "spine-01",
"ip": "192.168.1.1",
"port": 22,
"latitude": 51.5112179,
"longitude": -0.0048555,
"active": false,
},
}
// Print the entire map and data type
fmt.Printf("%+v, %T\n", inventory, inventory)
// Print data types for each element
for i := 0; i < len(inventory); i++ {
fmt.Printf("%+v, %T\n", inventory[i], inventory[i])
for k := range inventory[i] {
fmt.Printf("%v=%v, %T\n", k, inventory[i][k], inventory[i][k])
}
}
}
Here is the breakdown on what’s going on:
- Following the discussion in the previous blog post, we create a custom data type. We did it for structs, but it can be created for absolutely any other data type. The data type is “[]map[interface{}]interface{}”, meaning we create:
- List of maps
- where key can be anything
- and variable can be anything as well
- We print the variable and its type using “fmt.Printf()” function using “%T” interpolation, which returns the data type
- we loop through every map and print its data type
- Finally we loop through each key-value pair and print data types as well-being
Here is the result of execution of this Go (Glang) script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 $ go run .
[map[active:true ip:192.168.1.1 latitude:51.5120898 longitude:-0.0030987 name:leaf-01 os:cisco-nxos port:22] map[active:true ip:192.168.1.2 latitude:51.5120427 longitude:-0.0044585 name:leaf-02 os:arista-eos port:830] map[active:false ip:192.168.1.1 latitude:51.5112179 longitude:-0.0048555 name:spine-01 port:22]], main.Inventory
map[active:true ip:192.168.1.1 latitude:51.5120898 longitude:-0.0030987 name:leaf-01 os:cisco-nxos port:22], map[interface {}]interface {}
os=cisco-nxos, string
ip=192.168.1.1, string
port=22, int
latitude=51.5120898, float64
longitude=-0.0030987, float64
active=true, bool
name=leaf-01, string
map[active:true ip:192.168.1.2 latitude:51.5120427 longitude:-0.0044585 name:leaf-02 os:arista-eos port:830], map[interface {}]interface {}
latitude=51.5120427, float64
longitude=-0.0044585, float64
active=true, bool
name=leaf-02, string
os=arista-eos, string
ip=192.168.1.2, string
port=830, int
map[active:false ip:192.168.1.1 latitude:51.5112179 longitude:-0.0048555 name:spine-01 port:22], map[interface {}]interface {}
name=spine-01, string
ip=192.168.1.1, string
port=22, int
latitude=51.5112179, float64
longitude=-0.0048555, float64
active=false, bool
In the very first line, you can see that data type is “main.Inventory”, which refers to the name of our custom data type. However, once we go further, we see data types “map[interface {}]interface {}”, which refer to a single entry in inventory and then correctly identified string, integer, float and boolean data types. The correct data types allow you to perform various operations with the associated data (e.g., comparisons, Math operations, etc.).
Lessons in GitHub
You can find the final working versions of the files from this blog at out GitHub page.
Conclusion
Possibility to dynamically detect data types is a useful feature in any programming language. With Python being dynamically typed, this capability is built-in. In Go (Golang) you need to use interface to be able to achieve the same. The caution shall be exercised though, as using static types make application more logical, straightforward, and less error prone. Take care and good bye.
Support us
P.S.
If you have further questions or you need help with your networks, I’m happy to assist you, just send me message. Also don’t forget to share the article on your social media, if you like it.
BR,
Anton Karneliuk