How to

Follow instructions on this website to create Data Source Name (DSN) that securely stores your connection variables (user, password, connection information) in Windows so that a connection can be made from R just by calling the DSN ‘Alias’.

R

library(DBI)
con <- dbConnect(odbc::odbc(), dsn = "Bike_MySQL")
con
## <OdbcConnection> root@localhost via TCP/IP
##   Database: bikeometers_db
##   MySQL Version: 8.0.23

Python

import os
from sqlalchemy import create_engine

engine = create_engine(f'mysql+mysqldb://{os.environ["arlington_user"]}:{os.environ["arlington_password"]}@localhost/bikeometers_db', echo=False)

engine.connect()
## <sqlalchemy.engine.base.Connection object at 0x000000002E1A39E8>

Background

Publishing my finished projects and documenting every line of code I run to complete it is a great way for people to replicate/validate my work and also enables people to learn from my methods. The problem with publishing every line of code is when it’s time to login in to my database, I need a way to hide my credentials.

R

Originally, I made the connection using:

con <- dbConnect(odbc::odbc(), .connection_string = "Driver={MySQL ODBC 8.0 Unicode Driver};", 
                 server = "localhost", db = "bikeometers_db", user = "root", password = rstudioapi::askForPassword("Database password"))

However this requires me to enter in a password each time I wanted to Knit the document. This was only a minor inconvenience when Knitting individual documents but when I wanted to add another post to my website, I couldn’t use the ‘Build Website’ button in RStudio as it wouldn’t call the ‘rstudioapi::askForPassword’ function.

So, I needed a way to securely connect to my database from a open source RMarkdown document.

Enter the great (but surprisingly not the top result on Google) RStudio documentation. It lists ‘Integrated Security with DSN’ as the top method for securing your database credentials. A quick search for ‘r DSN’ brought me to this website which outlined how I could setup an ‘alias’ from the ‘ODBC Data Sources’ Windows app. Then from RStudio, I could call that DSN (Data Source Name) using the below code:

library(odbc)
con <- dbConnect(odbc::odbc(), dsn = "N_MySQL", db='bikeometers_db')

This would allow me to make the connection from within a published RMarkdown document without exposing my credentials.

I could even specify the database from within ODBC and just pass in the DSN name like below:

con <- dbConnect(odbc::odbc(), dsn = "Bike_MySQL")

Easy peasy!

Here’s a great post by Hadley Wickham that goes over different scenarios when you may want to store ‘secrets’ and how best to accomplish them.

Python in R

If you are including Python code to be run with the reticulate library, you have a few options and a few headaches.

PyODBC

PyODBC is an easy way to access your database via your DSN alias, eliminating the need to hard-code your credentials. However, I can’t figure out how to make it play nice with Pandas… see below.

SQLAlchemy + Pandas

Currently, I am using the Pandas method .to_sql() quite a lot to move my dataframes into MySQL. However, Pandas requires a SQLAlchemy ‘engine’ object. To create this ‘engine’ object I can’t use my DSN method to make the database connection. Do not hard-code your credentials like below.

engine = create_engine(f'mysql+mysqldb://newuser:donteventry@localhost/bikeometers_db', echo=False)

After some research there are other options. The most straight forward option seemed to be saving the username and password in a separate .py document which I could import like so:

from config.py import username, password
engine = create_engine(f'mysql+mysqldb://{username}:{password}@localhost/bikeometers_db', echo=False)

Obviously, just add this config.py document to the .gitignore file so it wont get uploaded to github.

Another option is to store the credentials in an environment variable.

You can see all your environment variables by using the following R code:

Sys.getenv()

Set an environment variable using:

Sys.setenv(username = "nathan")

Call an environment variable using:

Sys.getenv('username')
## [1] "nathan"

In Python you can use ‘os’ module to set and get environment variables.

To see all environment variables:

import os
print(os.environ)

To set an environment variable:

os.environ['username'] = 'nathan'

Call an environment variable using:

os.environ['username']
## 'nathan'

Both options have major downsides as any method where you record your credentials is not very secure. If I’m worrying about a bad actor gaining access to my computer, both credential storage methods leave me vulnerable while the ‘config.py’ method adds extra risk if the document is accidentally published to github.

After reading over the above linked articles and this article, I’ll choose using the environment variable in my project’s virtual environment as it reduces the risk of accidentally publishing my password to Github. After the project is published I can then remove the password from the environment variable.

LS0tDQp0aXRsZTogIlB1Ymxpc2ggUm1hcmtkb3duIERvY3VtZW50cyBXaXRoIERhdGFiYXNlIENvbm5lY3Rpb25zIFdpdGhvdXQgRXhwb3NpbmcgQ3JlZGVudGlhbHMiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBpbmNsdWRlczoNCiAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwNCi0tLQ0KXCANClwgDQoNCiMgSG93IHRvDQoNCkZvbGxvdyBpbnN0cnVjdGlvbnMgb24gW3RoaXMgd2Vic2l0ZV0oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAxOC8wNS9zZXR0aW5nLXVwLWFuLW9kYmMtY29ubmVjdGlvbi13aXRoLW1zLXNxbC1zZXJ2ZXItb24td2luZG93cy8pIHRvIGNyZWF0ZSBEYXRhIFNvdXJjZSBOYW1lIChEU04pIHRoYXQgc2VjdXJlbHkgc3RvcmVzIHlvdXIgY29ubmVjdGlvbiB2YXJpYWJsZXMgKHVzZXIsIHBhc3N3b3JkLCBjb25uZWN0aW9uIGluZm9ybWF0aW9uKSBpbiBXaW5kb3dzIHNvIHRoYXQgYSBjb25uZWN0aW9uIGNhbiBiZSBtYWRlIGZyb20gUiBqdXN0IGJ5IGNhbGxpbmcgdGhlIERTTiAnQWxpYXMnLg0KDQojIyMgUg0KDQpgYGB7cn0NCmxpYnJhcnkoREJJKQ0KY29uIDwtIGRiQ29ubmVjdChvZGJjOjpvZGJjKCksIGRzbiA9ICJCaWtlX015U1FMIikNCmNvbg0KYGBgDQoNCiMjIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCmltcG9ydCBvcw0KZnJvbSBzcWxhbGNoZW15IGltcG9ydCBjcmVhdGVfZW5naW5lDQoNCmVuZ2luZSA9IGNyZWF0ZV9lbmdpbmUoZidteXNxbCtteXNxbGRiOi8ve29zLmVudmlyb25bImFybGluZ3Rvbl91c2VyIl19Ontvcy5lbnZpcm9uWyJhcmxpbmd0b25fcGFzc3dvcmQiXX1AbG9jYWxob3N0L2Jpa2VvbWV0ZXJzX2RiJywgZWNobz1GYWxzZSkNCg0KZW5naW5lLmNvbm5lY3QoKQ0KYGBgDQoNCiMgQmFja2dyb3VuZA0KDQpQdWJsaXNoaW5nIG15IGZpbmlzaGVkIHByb2plY3RzIGFuZCBkb2N1bWVudGluZyBldmVyeSBsaW5lIG9mIGNvZGUgSSBydW4gdG8gY29tcGxldGUgaXQgaXMgYSBncmVhdCB3YXkgZm9yIHBlb3BsZSB0byByZXBsaWNhdGUvdmFsaWRhdGUgbXkgd29yayBhbmQgYWxzbyBlbmFibGVzIHBlb3BsZSB0byBsZWFybiBmcm9tIG15IG1ldGhvZHMuIFRoZSBwcm9ibGVtIHdpdGggcHVibGlzaGluZyBldmVyeSBsaW5lIG9mIGNvZGUgaXMgd2hlbiBpdCdzIHRpbWUgdG8gbG9naW4gaW4gdG8gbXkgZGF0YWJhc2UsIEkgbmVlZCBhIHdheSB0byBoaWRlIG15IGNyZWRlbnRpYWxzLg0KDQojIyMgUg0KDQpPcmlnaW5hbGx5LCBJIG1hZGUgdGhlIGNvbm5lY3Rpb24gdXNpbmc6DQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpjb24gPC0gZGJDb25uZWN0KG9kYmM6Om9kYmMoKSwgLmNvbm5lY3Rpb25fc3RyaW5nID0gIkRyaXZlcj17TXlTUUwgT0RCQyA4LjAgVW5pY29kZSBEcml2ZXJ9OyIsIA0KICAgICAgICAgICAgICAgICBzZXJ2ZXIgPSAibG9jYWxob3N0IiwgZGIgPSAiYmlrZW9tZXRlcnNfZGIiLCB1c2VyID0gInJvb3QiLCBwYXNzd29yZCA9IHJzdHVkaW9hcGk6OmFza0ZvclBhc3N3b3JkKCJEYXRhYmFzZSBwYXNzd29yZCIpKQ0KYGBgDQoNCkhvd2V2ZXIgdGhpcyByZXF1aXJlcyBtZSB0byBlbnRlciBpbiBhIHBhc3N3b3JkIGVhY2ggdGltZSBJIHdhbnRlZCB0byBLbml0IHRoZSBkb2N1bWVudC4gVGhpcyB3YXMgb25seSBhIG1pbm9yIGluY29udmVuaWVuY2Ugd2hlbiBLbml0dGluZyBpbmRpdmlkdWFsIGRvY3VtZW50cyBidXQgd2hlbiBJIHdhbnRlZCB0byBhZGQgYW5vdGhlciBwb3N0IHRvIG15IHdlYnNpdGUsIEkgY291bGRuJ3QgdXNlIHRoZSAnQnVpbGQgV2Vic2l0ZScgYnV0dG9uIGluIFJTdHVkaW8gYXMgaXQgd291bGRuJ3QgY2FsbCB0aGUgJ3JzdHVkaW9hcGk6OmFza0ZvclBhc3N3b3JkJyBmdW5jdGlvbi4NCg0KU28sIEkgbmVlZGVkIGEgd2F5IHRvICoqc2VjdXJlbHkqKiBjb25uZWN0IHRvIG15IGRhdGFiYXNlIGZyb20gYSAqKm9wZW4gc291cmNlKiogUk1hcmtkb3duIGRvY3VtZW50Lg0KDQpFbnRlciB0aGUgZ3JlYXQgKGJ1dCBzdXJwcmlzaW5nbHkgbm90IHRoZSB0b3AgcmVzdWx0IG9uIEdvb2dsZSkgW1JTdHVkaW8gZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9kYi5yc3R1ZGlvLmNvbS9iZXN0LXByYWN0aWNlcy9tYW5hZ2luZy1jcmVkZW50aWFscy8pLiBJdCBsaXN0cyAnSW50ZWdyYXRlZCBTZWN1cml0eSB3aXRoIERTTicgYXMgdGhlIHRvcCBtZXRob2QgZm9yIHNlY3VyaW5nIHlvdXIgZGF0YWJhc2UgY3JlZGVudGlhbHMuIEEgcXVpY2sgc2VhcmNoIGZvciAnciBEU04nIGJyb3VnaHQgbWUgdG8gW3RoaXMgd2Vic2l0ZV0oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAxOC8wNS9zZXR0aW5nLXVwLWFuLW9kYmMtY29ubmVjdGlvbi13aXRoLW1zLXNxbC1zZXJ2ZXItb24td2luZG93cy8pIHdoaWNoIG91dGxpbmVkIGhvdyBJIGNvdWxkIHNldHVwIGFuICdhbGlhcycgZnJvbSB0aGUgJ09EQkMgRGF0YSBTb3VyY2VzJyBXaW5kb3dzIGFwcC4gVGhlbiBmcm9tIFJTdHVkaW8sIEkgY291bGQgY2FsbCB0aGF0IERTTiAoRGF0YSBTb3VyY2UgTmFtZSkgdXNpbmcgdGhlIGJlbG93IGNvZGU6DQoNCmBgYHtyIGV2YWx1YXRlPUZBTFNFfQ0KbGlicmFyeShvZGJjKQ0KY29uIDwtIGRiQ29ubmVjdChvZGJjOjpvZGJjKCksIGRzbiA9ICJOX015U1FMIiwgZGI9J2Jpa2VvbWV0ZXJzX2RiJykNCmBgYA0KDQpUaGlzIHdvdWxkIGFsbG93IG1lIHRvIG1ha2UgdGhlIGNvbm5lY3Rpb24gZnJvbSB3aXRoaW4gYSBwdWJsaXNoZWQgUk1hcmtkb3duIGRvY3VtZW50IHdpdGhvdXQgZXhwb3NpbmcgbXkgY3JlZGVudGlhbHMuDQoNCkkgY291bGQgZXZlbiAqKnNwZWNpZnkgdGhlIGRhdGFiYXNlKiogZnJvbSB3aXRoaW4gT0RCQyBhbmQganVzdCBwYXNzIGluIHRoZSBEU04gbmFtZSBsaWtlIGJlbG93Og0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KY29uIDwtIGRiQ29ubmVjdChvZGJjOjpvZGJjKCksIGRzbiA9ICJCaWtlX015U1FMIikNCmBgYA0KDQohW10oaW1hZ2VzL09EQkMlMjBEYXRhJTIwU291cmNlJTIwQ29uZmlnLmpwZyAiT0RCQyBkYXRhIHNvdXJjZSBjb25maWciKQ0KDQpFYXN5IHBlYXN5IQ0KDQpbSGVyZSdzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaHR0ci92aWduZXR0ZXMvc2VjcmV0cy5odG1sKSBhIGdyZWF0IHBvc3QgYnkgSGFkbGV5IFdpY2toYW0gdGhhdCBnb2VzIG92ZXIgZGlmZmVyZW50IHNjZW5hcmlvcyB3aGVuIHlvdSBtYXkgd2FudCB0byBzdG9yZSAnc2VjcmV0cycgYW5kIGhvdyBiZXN0IHRvIGFjY29tcGxpc2ggdGhlbS4NCg0KIyMjIFB5dGhvbiBpbiBSDQoNCklmIHlvdSBhcmUgaW5jbHVkaW5nIFB5dGhvbiBjb2RlIHRvIGJlIHJ1biB3aXRoIHRoZSAqcmV0aWN1bGF0ZSogbGlicmFyeSwgeW91IGhhdmUgYSBmZXcgb3B0aW9ucyBhbmQgYSBmZXcgaGVhZGFjaGVzLg0KDQojIyMgUHlPREJDDQoNClB5T0RCQyBpcyBhbiBlYXN5IHdheSB0byBhY2Nlc3MgeW91ciBkYXRhYmFzZSB2aWEgeW91ciBEU04gYWxpYXMsIGVsaW1pbmF0aW5nIHRoZSBuZWVkIHRvIGhhcmQtY29kZSB5b3VyIGNyZWRlbnRpYWxzLiBIb3dldmVyLCBJIGNhbid0IGZpZ3VyZSBvdXQgaG93IHRvIG1ha2UgaXQgcGxheSBuaWNlIHdpdGggUGFuZGFzLi4uIHNlZSBiZWxvdy4NCg0KIyMjIFNRTEFsY2hlbXkgKyBQYW5kYXMNCg0KQ3VycmVudGx5LCBJIGFtIHVzaW5nIHRoZSBQYW5kYXMgbWV0aG9kIC50b19zcWwoKSBxdWl0ZSBhIGxvdCB0byBtb3ZlIG15IGRhdGFmcmFtZXMgaW50byBNeVNRTC4gSG93ZXZlciwgUGFuZGFzIHJlcXVpcmVzIGEgU1FMQWxjaGVteSAnZW5naW5lJyBvYmplY3QuIFRvIGNyZWF0ZSB0aGlzICdlbmdpbmUnIG9iamVjdCBJIGNhbid0IHVzZSBteSBEU04gbWV0aG9kIHRvIG1ha2UgdGhlIGRhdGFiYXNlIGNvbm5lY3Rpb24uICoqRG8gbm90KiogaGFyZC1jb2RlIHlvdXIgY3JlZGVudGlhbHMgbGlrZSBiZWxvdy4NCg0KYGBge3B5dGhvbiBldmFsPUZBTFNFfQ0KZW5naW5lID0gY3JlYXRlX2VuZ2luZShmJ215c3FsK215c3FsZGI6Ly9uZXd1c2VyOmRvbnRldmVudHJ5QGxvY2FsaG9zdC9iaWtlb21ldGVyc19kYicsIGVjaG89RmFsc2UpDQpgYGANCg0KQWZ0ZXIgc29tZSByZXNlYXJjaCB0aGVyZSBhcmUgb3RoZXIgb3B0aW9ucy4gVGhlIG1vc3Qgc3RyYWlnaHQgZm9yd2FyZCBbb3B0aW9uXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL2EvMjM5NzkwNSkgc2VlbWVkIHRvIGJlIHNhdmluZyB0aGUgdXNlcm5hbWUgYW5kIHBhc3N3b3JkIGluIGEgc2VwYXJhdGUgLnB5IGRvY3VtZW50IHdoaWNoIEkgY291bGQgaW1wb3J0IGxpa2Ugc286DQoNCmBgYHtweXRob24gZXZhbD1GQUxTRX0NCmZyb20gY29uZmlnLnB5IGltcG9ydCB1c2VybmFtZSwgcGFzc3dvcmQNCmVuZ2luZSA9IGNyZWF0ZV9lbmdpbmUoZidteXNxbCtteXNxbGRiOi8ve3VzZXJuYW1lfTp7cGFzc3dvcmR9QGxvY2FsaG9zdC9iaWtlb21ldGVyc19kYicsIGVjaG89RmFsc2UpDQoNCmBgYA0KDQpPYnZpb3VzbHksIGp1c3QgYWRkIHRoaXMgY29uZmlnLnB5IGRvY3VtZW50IHRvIHRoZSAuZ2l0aWdub3JlIGZpbGUgc28gaXQgd29udCBnZXQgdXBsb2FkZWQgdG8gZ2l0aHViLg0KDQpBbm90aGVyIFtvcHRpb25dKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vYS8zMDY2NDMxOCkgaXMgdG8gc3RvcmUgdGhlIGNyZWRlbnRpYWxzIGluIGFuIGVudmlyb25tZW50IHZhcmlhYmxlLg0KDQpZb3UgY2FuIHNlZSBhbGwgeW91ciBlbnZpcm9ubWVudCB2YXJpYWJsZXMgYnkgdXNpbmcgdGhlIGZvbGxvd2luZyBSIGNvZGU6DQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpTeXMuZ2V0ZW52KCkNCmBgYA0KDQpTZXQgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgdXNpbmc6DQoNCmBgYHtyfQ0KU3lzLnNldGVudih1c2VybmFtZSA9ICJuYXRoYW4iKQ0KYGBgDQoNCkNhbGwgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgdXNpbmc6DQoNCmBgYHtyfQ0KU3lzLmdldGVudigndXNlcm5hbWUnKQ0KYGBgDQoNCkluIFB5dGhvbiB5b3UgY2FuIHVzZSAnb3MnIG1vZHVsZSB0byBzZXQgYW5kIGdldCBlbnZpcm9ubWVudCB2YXJpYWJsZXMuDQoNClRvIHNlZSBhbGwgZW52aXJvbm1lbnQgdmFyaWFibGVzOg0KDQpgYGB7cHl0aG9uIGV2YWw9RkFMU0V9DQppbXBvcnQgb3MNCnByaW50KG9zLmVudmlyb24pDQpgYGANCg0KVG8gc2V0IGFuIGVudmlyb25tZW50IHZhcmlhYmxlOg0KYGBge3B5dGhvbn0NCm9zLmVudmlyb25bJ3VzZXJuYW1lJ10gPSAnbmF0aGFuJw0KYGBgDQoNCkNhbGwgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUgdXNpbmc6DQoNCmBgYHtweXRob259DQpvcy5lbnZpcm9uWyd1c2VybmFtZSddDQpgYGANCg0KQm90aCBvcHRpb25zIGhhdmUgbWFqb3IgZG93bnNpZGVzIGFzIGFueSBtZXRob2Qgd2hlcmUgeW91IHJlY29yZCB5b3VyIGNyZWRlbnRpYWxzIGlzIG5vdCB2ZXJ5IHNlY3VyZS4gSWYgSSdtIHdvcnJ5aW5nIGFib3V0IGEgYmFkIGFjdG9yIGdhaW5pbmcgYWNjZXNzIHRvIG15IGNvbXB1dGVyLCBib3RoIGNyZWRlbnRpYWwgc3RvcmFnZSBtZXRob2RzIGxlYXZlIG1lIHZ1bG5lcmFibGUgd2hpbGUgdGhlICdjb25maWcucHknIG1ldGhvZCBhZGRzIGV4dHJhIHJpc2sgaWYgdGhlIGRvY3VtZW50IGlzIGFjY2lkZW50YWxseSBwdWJsaXNoZWQgdG8gZ2l0aHViLg0KDQpBZnRlciByZWFkaW5nIG92ZXIgdGhlIGFib3ZlIGxpbmtlZCBhcnRpY2xlcyBhbmQgW3RoaXNdKGh0dHBzOi8vd3d3LjEyZmFjdG9yLm5ldC9jb25maWcpIGFydGljbGUsIEknbGwgY2hvb3NlIHVzaW5nIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZSBpbiBteSBwcm9qZWN0J3MgdmlydHVhbCBlbnZpcm9ubWVudCBhcyBpdCByZWR1Y2VzIHRoZSByaXNrIG9mIGFjY2lkZW50YWxseSBwdWJsaXNoaW5nIG15IHBhc3N3b3JkIHRvIEdpdGh1Yi4gQWZ0ZXIgdGhlIHByb2plY3QgaXMgcHVibGlzaGVkIEkgY2FuIHRoZW4gcmVtb3ZlIHRoZSBwYXNzd29yZCBmcm9tIHRoZSBlbnZpcm9ubWVudCB2YXJpYWJsZS4NCg==