Skip to content

Commit

Permalink
initial client covering consumer Octopus REST API endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
dwillcocks committed May 16, 2021
1 parent 6809cfc commit fba9d64
Show file tree
Hide file tree
Showing 23 changed files with 1,841 additions and 1 deletion.
Binary file added .docs/assets/workswith.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github: [dwillcocks]
custom: https://share.octopus.energy/dusk-shark-465
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
assignees:
- "dwillcocks"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
assignees:
- "dwillcocks"
34 changes: 34 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: tests

on:
push:

jobs:
build:
strategy:
matrix:
go-version: [1.15.x, 1.16.x]

runs-on: ubuntu-latest

steps:
- name: Checkout
id: checkout
uses: actions/checkout@v2.3.4

- name: Set up Go
id: installGo
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.version }}

- name: Lint
id: lint
uses: golangci/golangci-lint-action@v2
with:
skip-go-installation: true

- name: GoGoGo
run: |
go build ./...
go test ./...
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
example/
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
# octopus_energy_client_go
<p align=center>
<img alt=logo src="https://github.com/danopstech/octopusenergy/raw/main/.docs/assets/workswith.png" height=150 />
<h3 align=center>Octopus Energy Golang API client</h3>
</p>

---
[![PkgGoDev](https://pkg.go.dev/badge/github.com/danopstech/octopusenergy/)](https://pkg.go.dev/github.com/danopstech/octopusenergy/)
[![License](https://img.shields.io/github/license/danopstech/octopusenergy)](/LICENSE)
[![Release](https://img.shields.io/github/release/danopstech/octopusenergy.svg)](https://github.com/danopstech/octopusenergy/releases/latest)
[![tests](https://github.com/danopstech/octopusenergy/actions/workflows/build.yaml/badge.svg)](https://github.com/danopstech/octopusenergy/actions/workflows/build.yaml)

This package provides a Golang client to [Octopus Energy's API](https://developer.octopus.energy/docs/api/). Octopus Energy provides a REST API for customers to interact with our platform. Amongst other things, it provides functionality for:

- Browsing energy products, tariffs and their charges.
- Retrieving details about a UK electricity meter-point.
- Browsing the half-hourly consumption of an electricity or gas meter.
- Determining the grid-supply-point (GSP) for a UK postcode.

If you are an Octopus Energy customer, you can generate an API key from your [online dashboard](https://octopus.energy/dashboard/developer/).

### Authentication
Authentication is required for all API end-points when using this API client. This is performed via [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). This is configured when you instantiate a new client with a config object.
**Warning: Do not share your secret API keys with anyone.**

### Not an Octopus Energy customer?
Please read about the Octopus tariffs and ensure they are right for you, if you think they are, then please use my [referral link](https://share.octopus.energy/dusk-shark-465). (at the time of writing this we will both receive £50 credit)

### Usage
More in the examples folder

```golang
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()

var netClient = http.Client{
Timeout: time.Second * 10,
}

client := octopusenergy.NewClient(octopusenergy.NewConfig().
WithApiKeyFromEnvironments().
WithHTTPClient(netClient),
)

consumption, err := client.Consumption.GetPagesWithContext(ctx, &octopusenergy.ConsumptionGetOptions{
MPN: "1111111111", // <--- replace
SerialNumber: "1111111111", // <--- replace
FuelType: octopusenergy.FuelTypeElectricity,
PeriodFrom: octopusenergy.Time(time.Now().Add(-48 * time.Hour)),
})

if err != nil {
log.Fatalf("failed to getting consumption: %s", err.Error())
}
```

### Links
- [Octopus Energy API Docs](https://developer.octopus.energy/docs/api/)
- [Get API Key](https://octopus.energy/dashboard/developer/)
64 changes: 64 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package octopusenergy

import (
"log"
"net/http"
"os"
)

// Config provides service configuration for client.
type Config struct {
// Required for Authentication on all API end-points when using this client.
// If you are an Octopus Energy customer, you can generate an API key from your online dashboard
// https://octopus.energy/dashboard/developer/.
ApiKey string

// All API requests will use this base URL.
Endpoint *string

// The HTTP client to use when sending requests. Defaults to `http.DefaultClient`.
HTTPClient *http.Client
}

// NewConfig returns a new Config pointer that can be chained with builder
// methods to set multiple configuration values inline without using pointers.
//
// client := octopusenergy.NewClient(octopusenergy.NewConfig().
// WithApiKey("your-api-key"),
// ))
func NewConfig() *Config {
return &Config{}
}

// WithApiKey sets a config ApiKey value returning a Config pointer for chaining.
func (c *Config) WithApiKey(apiKey string) *Config {
c.ApiKey = apiKey
return c
}

// WithApiKeyFromEnvironments sets a config ApiKey value from environments valuable
// returning a Config pointer for chaining.
func (c *Config) WithApiKeyFromEnvironments() *Config {
apiKey, ok := os.LookupEnv(apiKeyEnvKey)
if !ok {
log.Fatalln("could not find api key in environment variable 'OCTOPUS_ENERGY_API_KEY'")
}
if apiKey == "" {
log.Fatalln("the api key in environment variable 'OCTOPUS_ENERGY_API_KEY' is blank")
}

c.ApiKey = apiKey
return c
}

// WithEndpoint sets a config Endpoint value returning a Config pointer for chaining.
func (c *Config) WithEndpoint(endpoint string) *Config {
c.Endpoint = &endpoint
return c
}

// WithHTTPClient sets a config HTTP Client value returning a Config pointer for chaining.
func (c *Config) WithHTTPClient(HTTPClient http.Client) *Config {
c.HTTPClient = &HTTPClient
return c
}
121 changes: 121 additions & 0 deletions consumption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package octopusenergy

import (
"context"
"fmt"
"net/http"
"net/url"
"time"
)

// ConsumptionService handles communication with the consumption related Octopus API.
type ConsumptionService service

// ConsumptionGetOptions is the options for GetConsumption.
type ConsumptionGetOptions struct {
// The Meter Point Number this is the electricity meter-point’s MPAN or gas meter-point’s MPRN
MPN string `url:"-"`

// The meter’s serial number.
SerialNumber string `url:"-"`

// Fueltype: electricity or gas
FuelType FuelType `url:"-"`

// Show consumption from the given datetime (inclusive). This parameter can be provided on its own.
PeriodFrom *time.Time `url:"period_from,omitempty" layout:"2006-01-02T15:04:05Z" optional:"true"`

// Show consumption to the given datetime (exclusive).
// This parameter also requires providing the period_from parameter to create a range.
PeriodTo *time.Time `url:"period_to,omitempty" layout:"2006-01-02T15:04:05Z" optional:"true"`

// Page size of returned results.
// Default is 100, maximum is 25,000 to give a full year of half-hourly consumption details.
PageSize *int `url:"page_size,omitempty" optional:"true"`

// Ordering of results returned.
// Default is that results are returned in reverse order from latest available figure.
// Valid values: * ‘period’, to give results ordered forward. * ‘-period’, (default), to give results ordered from most recent backwards.
OrderBy *string `url:"order_by,omitempty" optional:"true"`

// Aggregates consumption over a specified time period.
// A day is considered to start and end at midnight in the server’s timezone.
// The default is that consumption is returned in half-hour periods. Accepted values are: * ‘hour’ * ‘day’ * ‘week’ * ‘month’ * ‘quarter’
GroupBy *string `url:"group_by,omitempty" optional:"true"`

// Pagination page to be returned on this request
Page *int `url:"page,omitempty" optional:"true"`
}

// ConsumptionGetOutput is the returned struct from GetConsumption.
type ConsumptionGetOutput struct {
Count int `json:"count"`
Next string `json:"next"`
Previous string `json:"previous"`
Results []struct {
Consumption float64 `json:"consumption"`
IntervalStart string `json:"interval_start"`
IntervalEnd string `json:"interval_end"`
} `json:"results"`
}

// Get consumption data for give meter details. This endpoint is paginated, it will return
// next and previous links if returned data is larger than the set page size, you are responsible
// to request the next page if required.
func (s *ConsumptionService) Get(options *ConsumptionGetOptions) (*ConsumptionGetOutput, error) {
return s.GetWithContext(context.Background(), options)
}

// GetWithContext same as Get except it takes a Context.
func (s *ConsumptionService) GetWithContext(ctx context.Context, options *ConsumptionGetOptions) (*ConsumptionGetOutput, error) {
path := fmt.Sprintf("v1/%s-meter-points/%s/meters/%s/consumption", options.FuelType.String(), options.MPN, options.SerialNumber)
rel := &url.URL{Path: path}
u := s.client.BaseURL.ResolveReference(rel)
url, err := addParameters(u, options)
if err != nil {
return nil, err
}

req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
if err != nil {
return nil, err
}

res := ConsumptionGetOutput{}
if err := s.client.sendRequest(req, &res); err != nil {
return nil, err
}

return &res, nil
}

// GetPages same as Get except it returns all pages in one request.
func (s *ConsumptionService) GetPages(options *ConsumptionGetOptions) (*ConsumptionGetOutput, error) {
return s.GetPagesWithContext(context.Background(), options)
}

// GetPagesWithContext same as GetPages except it takes a Context.
func (s *ConsumptionService) GetPagesWithContext(ctx context.Context, options *ConsumptionGetOptions) (*ConsumptionGetOutput, error) {
options.PageSize = Int(1500)
options.Page = nil

fullResp := ConsumptionGetOutput{}
var lastPage bool
var i int

for !lastPage {
page, err := s.GetWithContext(ctx, options)
if err != nil {
return nil, err
}
fullResp.Count = page.Count
fullResp.Results = append(fullResp.Results, page.Results...)
if page.Next == "" {
lastPage = true
}
i++
options.Page = Int(i)
}

return &fullResp, nil
}
33 changes: 33 additions & 0 deletions convert_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package octopusenergy

import (
"time"
)

// String returns a pointer to the string value passed in.
// This is a helper function when you need to provide pointers to
// optional fields in the input options object.
func String(v string) *string {
return &v
}

// Int returns a pointer to the int value passed in.
// This is a helper function when you need to provide pointers to
// optional fields in the input options object.
func Int(v int) *int {
return &v
}

// Bool returns a pointer to the bool value passed in.
// This is a helper function when you need to provide pointers to
// optional fields in the input options object.
func Bool(v bool) *bool {
return &v
}

// Time returns a pointer to the time.Time value passed in.
// This is a helper function when you need to provide pointers to
// optional fields in the input options object.
func Time(v time.Time) *time.Time {
return &v
}
33 changes: 33 additions & 0 deletions convert_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package octopusenergy_test

import (
"fmt"
"time"

"github.com/danopstech/octopusenergy"
)

func ExampleBool() {
boolPtr := octopusenergy.Bool(true)
fmt.Println(*boolPtr)
// Output: true
}

func ExampleString() {
stringPtr := octopusenergy.String("example")
fmt.Println(*stringPtr)
// Output: example
}

func ExampleInt() {
intPtr := octopusenergy.Int(5)
fmt.Println(*intPtr)
// Output: 5
}

func ExampleTime() {
wayBack := time.Date(1974, time.May, 19, 1, 2, 3, 4, time.UTC)
timePtr := octopusenergy.Time(wayBack)
fmt.Println(*timePtr)
// Output: 1974-05-19 01:02:03.000000004 +0000 UTC
}
6 changes: 6 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package octopusenergy

// errorResponse is the returned body when API error accrues
type errorResponse struct {
Detail string `json:"detail"`
}
Loading

0 comments on commit fba9d64

Please sign in to comment.