-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #155 from EasyPost/concurrent_insurance_buy
feat: adds concurrent_insurance_buy go script
- Loading branch information
Showing
6 changed files
with
276 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Concurrently Buy Insurance | ||
|
||
This script allows you to concurrently buy EasyPost Insurance. It works when insuring EasyPost Shipments or when buying standalone Insurance for a package bought outside of the EasyPost ecosystem. Use the `sample.csv` file as a template, fill in the details (do not delete the header), and run the script. | ||
|
||
## Things to Know | ||
|
||
- You must follow the `sample.csv` template or the script will not work correctly. Do not delete the header row | ||
- Only run the script once! Running the script multiple times with the same data will create duplicate insurance objects and fees | ||
- The script will spin up 20 concurrent requests at a time | ||
- Because requests are sent concurrently, they may complete in a different order than they were specified in the original CSV. Sorting the results CSV by Tracking Code and the original `sample.csv` will allow you to match up requests with input data for debugging purchases in the event of errors | ||
- The status of each request will be saved to a CSV once the script is complete. Each line will include error messages if there were any, the time each request took, and the status of the request | ||
- If there are errors reported in the results CSV, correct input data as necessary and run the script again | ||
- NOTE: Ensure you delete successful rows from the `sample.csv` file before running the script again. Failure to do so will result in duplicate insurance objects and fees (eg: If I had 5 rows, 3 succeeded, 2 failed - I would remove the 3 rows with a success status of true so that my sample CSV contained the 2 rows that initially failed, I'd correct the data as needed, and re-run the script with only those two failed rows) | ||
- It's recommended to run the `sample.csv` file as-is with a test API key to get a feel for how it works prior to loading real data and using a production API key | ||
|
||
## Usage | ||
|
||
```shell | ||
EASYPOST_API_KEY=123... CSV=path/to/sample.csv go run concurrent_insurance_buy.go | ||
``` | ||
|
||
## Development | ||
|
||
```shell | ||
# To build a standalone binary of this tool (eg: call it bulkins) | ||
go build -o bulkins | ||
``` |
168 changes: 168 additions & 0 deletions
168
community/golang/concurrent_insurance_buy/concurrent_insurance_buy.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/csv" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math" | ||
"os" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/EasyPost/easypost-go/v4" | ||
) | ||
|
||
// Concurrently buy EasyPost Insurance via a CSV file | ||
// Usage: EASYPOST_API_KEY=123... CSV=sample.csv go run concurrent_insurance_buy.go | ||
|
||
func main() { | ||
scriptStart := time.Now() | ||
|
||
records := getCsvRecords() | ||
|
||
apiKeyParam := os.Getenv("EASYPOST_API_KEY") | ||
if apiKeyParam == "" { | ||
handleGoErr(errors.New("EASYPOST_API_KEY param not set")) | ||
} | ||
|
||
client := easypost.New(apiKeyParam) | ||
numOfGoroutines := int(math.Min(float64(len(records)), 20)) | ||
semaphore := make(chan bool, numOfGoroutines) | ||
lineMessageList := make([][]string, 0) | ||
lineMessageList = append(lineMessageList, []string{"Tracking Code", "Reference", "Time Elapsed", "Success", "Message"}) | ||
|
||
// Iterate over our set of data and create an Insurance record for each line in the CSV | ||
for i, line := range records { | ||
semaphore <- true | ||
|
||
go func(lineNumber int, currentLine []string) { | ||
goroutineStartTime := time.Now() | ||
|
||
tracking_code := currentLine[0] | ||
reference := currentLine[1] | ||
carrier_string := currentLine[2] | ||
amount := currentLine[3] | ||
to_address_id := currentLine[4] | ||
from_address_id := currentLine[5] | ||
|
||
fmt.Printf("Sending request for %s...\n", tracking_code) | ||
success, message := createInsurance(client, tracking_code, reference, carrier_string, amount, to_address_id, from_address_id) | ||
|
||
elapsedTime := time.Since(goroutineStartTime) | ||
lineMessage := []string{tracking_code, reference, elapsedTime.String(), success, message} | ||
lineMessageList = append(lineMessageList, lineMessage) | ||
|
||
<-semaphore | ||
}(i, line) | ||
} | ||
|
||
// Gather up goroutines | ||
for i := 0; i < cap(semaphore); i++ { | ||
semaphore <- true | ||
} | ||
|
||
file, err := os.Create("insurance_buy_results.csv") | ||
handleGoErr(err) | ||
defer file.Close() | ||
writer := csv.NewWriter(file) | ||
defer writer.Flush() | ||
writer.WriteAll(lineMessageList) | ||
|
||
elapsedTotal := time.Since(scriptStart) | ||
fmt.Printf("\nTotal time elapsed: %s\n", elapsedTotal) | ||
} | ||
|
||
// getCsvRecords builds the set of data without including the header and validates it | ||
func getCsvRecords() [][]string { | ||
csvParam := os.Getenv("CSV") | ||
if csvParam == "" { | ||
handleGoErr(errors.New("CSV parameter not set")) | ||
} | ||
|
||
file, err := os.Open(csvParam) | ||
handleGoErr(err) | ||
defer file.Close() | ||
reader := csv.NewReader(file) | ||
|
||
lineNumber := 0 | ||
records := make([][]string, 0) | ||
for { | ||
record, err := reader.Read() | ||
// Ensure rows have valid data | ||
if err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
handleGoErr(err) | ||
} | ||
if len(record) != 6 { | ||
handleGoErr(err) | ||
} | ||
|
||
// Skip header | ||
if lineNumber == 0 { | ||
lineNumber++ | ||
continue | ||
} | ||
|
||
// Ensure `Amount` column is a valid float | ||
_, err = strconv.ParseFloat(record[3], 64) | ||
handleGoErr(err) | ||
|
||
lineNumber++ | ||
records = append(records, record) | ||
} | ||
|
||
return records | ||
} | ||
|
||
// handleGoErr prints the error to stderr and exits, should be used for Go errors and not EasyPost errors | ||
func handleGoErr(err error) { | ||
if err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// createInsurance creates an Insurance based on if the Tracker has a Shipment or not | ||
func createInsurance(client *easypost.Client, tracking_code string, reference string, carrier_string string, amount string, to_address_id string, from_address_id string) (string, string) { | ||
// We first get the tracker to know if there is a Shipment or not so we know which Insurance endpoint to hit | ||
tracker, err := client.GetTracker(tracking_code) | ||
var eperr *easypost.APIError | ||
if errors.As(err, &eperr) { | ||
return "false", eperr.Message | ||
} | ||
|
||
if tracker.ShipmentID != "" { | ||
_, err := client.InsureShipment(tracker.ShipmentID, amount) | ||
var eperr *easypost.APIError | ||
if errors.As(err, &eperr) { | ||
return "false", eperr.Message | ||
} | ||
|
||
return "true", "" | ||
} else { | ||
// If no shipment ID, it's a standalone insurance | ||
_, err := client.CreateInsurance( | ||
&easypost.Insurance{ | ||
ToAddress: &easypost.Address{ | ||
ID: to_address_id, | ||
}, | ||
FromAddress: &easypost.Address{ | ||
ID: from_address_id, | ||
}, | ||
Reference: reference, | ||
Carrier: carrier_string, | ||
TrackingCode: tracking_code, | ||
Amount: amount, | ||
}, | ||
) | ||
var eperr *easypost.APIError | ||
if errors.As(err, &eperr) { | ||
return "false", eperr.Message | ||
} | ||
|
||
return "true", "" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/EasyPost/examples/community/golang/concurrent_insurance_buy | ||
|
||
go 1.16 | ||
|
||
require github.com/EasyPost/easypost-go/v4 v4.6.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
github.com/EasyPost/easypost-go/v4 v4.6.0 h1:wxMK4wkGEG5vW/4Vdy3rwE9iqww1eQ1xS6oYWUZbhrc= | ||
github.com/EasyPost/easypost-go/v4 v4.6.0/go.mod h1:WGoS4tmjHquhooMNmY6RirP+KWeYV/akcf/Jg9Q6fsk= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= | ||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= | ||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= | ||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= | ||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= | ||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= | ||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Tracking Code,Reference (optional),Carrier String,Amount,To Address ID (optional),From Address ID (optional) | ||
EZ1000000001,ref_1,USPS,100.00,, | ||
EZ2000000002,ref_2,USPS,200.00,, | ||
EZ3000000003,,USPS,300.00,, | ||
EZ4000000004,,USPS,400.00,, | ||
EZ5000000005,,USPS,500.00,, | ||
EZ6000000006,,USPS,600.00,, | ||
EZ7000000007,,USPS,700.00,, |