config.yml в R на примере работы с Базой ДанныхДля корректного применения программного кода в разных случаях использования, например,
для обучения (разработки модели),
тестирования, а затем
развертывания,
необходимо использовать отличные для каждой вышеперечисленных целей предустановочные (конфигурационных) значения. Поэтому для подобных целей, а также для отдельного хранения паролей и иных констант, которые следует поберечь от чужих глаз, целесообразно загружать данные из текстовых файлов форматов YAML или JSON. Первый имеет формат применяемый для задания начальных параметров в R Markdown и R Notebook файлах (*.Rmd).
В составе R/RStudio имеется пакет config, разработанный J. J. Allaire в компании RStudio, Inc. Его грамотное использование способно существенно облегчить работу с различными наборами предустановленных значений. Он загружает и манипулирует вариантами констант, сохраненных обычно в файл config.yml в текущей или родительской папке, если не указано иное. По крайней мере, один по умолчанию (англ. default).
config.ymlОбычно применяют три набора значений:
Для среды ОБУЧЕНИЯ не задаватьте НИЧЕГО, это тоже, что дать опцию ‘default’ в .Renviron, Renviron.site , например, R_CONFIG_ACTIVE = 'default'.
Для среды ТЕСТИРОВАНИЯ задайте установку ‘test’ в Renviron.site / Rprofile.site, например, R_CONFIG_ACTIVE = 'test'.
Для среды РАЗВЕРТЫВАНИЯ задайте установку ‘production’ в Renviron.site / Rprofile.site, например, R_CONFIG_ACTIVE = 'production'.
Образец файла config.yml с подобной структурой приведен ниже:
# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r
default:
date : !expr as.Date('2020-05-01')
seed : 2020
debug : ON
datawarehouse :
driver: 'PostgreSQL'
dbname: 'gpdb'
host : '176.54.3.21'
port : 4321
user : 'user'
pwd : [0x70, 0x61, 0x73, 0x73]
# pwd : !expr rstudioapi::askForPassword('Password for PostgreSQL')
# pwd : !expr Sys.getenv('PGPWD') # value from .Renviron, .Rprofile, Renviron.site or Rprofile.site files
# keyring - Access the System Credential Store from R: https://www.r-bloggers.com/how-to-avoid-publishing-credentials-in-your-code
options: '-c search_path=ANL'
comments: >
Для среды ОБУЧЕНИЯ не задаватьте НИЧЕГО, это тоже, что дать опцию 'default' в Renviron.site / Rprofile.site, например, R_CONFIG_ACTIVE = 'default' .
Для среды ТЕСТИРОВАНИЯ дайте установку 'test' в Renviron.site / Rprofile.site, например, R_CONFIG_ACTIVE = 'test' .
Для среды РАЗВЕРТЫВАНИЯ дайте установку 'production' в Renviron.site / Rprofile.site, например, R_CONFIG_ACTIVE = 'production' .
Смотри информацию о "R Startup" (https://stat.ethz.ch/R-manual/R-devel/library/base/html/Startup.html) по этим файлам.
server : 'localhost:7600'
test:
debug : OFF
production:
inherits: test
debug : !expr Sys.getenv('ENABLE_DEBUG') == '1'
# server : !expr config::get(value = 'server', file = '/etc/server-config.yml')
server : 'production.***.kz:7651'
Если компания применяет RStudio Connect - платформу для коллективной работе с базами данных на Lunix, которые, кроме всего прочего, имеют Professional Drivers, то тогда наряду с опцией default следует задавать набор предустановленных значений для rsconnect.
config.yml файлаОбычно загрузка YAML файла производится из текущей папки, но можно задавать как другие папки, так и иные имена файлов, желательно с расширением *.yml.
Зачастую отдельные константы, помещаемые в config.yml файл, берутся из других файлов:
.Renviron;
.Rprofile;
Renviron.site и
Rprofile.site.
Подробности о .Renviron и им подобных файлах смотрите в материале “R Startup”. А вот файлы .Rprofile или Rprofile.site не рекомендую, так как считанные из них значения становятся переменными в R. Их можно легко считывать в R, тогда как загруженные из .Renviron остаются переменными окружения (англ. environment variables) и для их извлечения нужны определенные манипуляции.
Учтите, что данные из вышеперечисленных файлов считываются в начале сеанса R, а для файлов с переменными окружения (.Renviron и Renviron.site) остаются неизменными во время всего сеанса R.
При этом файлы Renviron.site и Rprofile.site используются непосредство в RStudio Server и относятся к конкретной версии R, поэтому они могут быть в папках каждой из версий программы. Особенности использования стратегии управления RStudio Server описаны в R for Enterprise: Understanding R’s Startup.
При необходимости в YAML файле можно непосредственно выполнить код R предварив его командой !expr.
# Use Configuration Values from YAML File `config.yml`
library('config') # Manage Environment Specific Configuration Values##
## Attaching package: 'config'
## The following objects are masked from 'package:base':
##
## get, merge
# set the active configuration globally via .Renviron or .Rprofile
# Sys.setenv(R_CONFIG_ACTIVE = 'production') # For 'production' settings
# see https://support.rstudio.com/hc/en-us/articles/360047157094-Managing-R-with-Rprofile-Renviron-Rprofile-site-Renviron-site-rsession-conf-and-repos-conf
try_Config_Get <- function(value = NULL, config = Sys.getenv('R_CONFIG_ACTIVE', 'default'),
file = Sys.getenv('R_CONFIG_FILE', 'config.yml'), use_parent = TRUE) {
out <-
tryCatch(
expr = {
result <- suppressWarnings( config::get(value, config, file, use_parent) )
return( result )
}, # end of `expr`
error = function(e){
message('Unsuccessfully executed the config::get() call!!!')
print(e)
return( NULL )
}, # end of `error`
finally = {
message('All done in `config::get()`.')
} # end of `finally`
)
return( out )
} # End of function `try_Config_Get()`
# Load a Default Set of Configuration Values from YAML File `config.yml`
config <- try_Config_Get()## All done in `config::get()`.
# config <- suppressWarnings( config::get() ); str(config) # from the parent directory of Project on ` file = 'config.yml' `
if ( exists('config') & is.null(config) == FALSE ) {
writeLines('The Specific Configuration Values:')
str(config)
writeLines('The end of Specific Configuration Values. \n')
# Show Configuration comments from YAML File `config.yml` on the Configuration Sets of Values (Sections)
# writeLines(config$comments)
}## The Specific Configuration Values:
## List of 6
## $ datawarehouse:List of 7
## ..$ driver : chr "PostgreSQL"
## ..$ dbname : chr "gpdb"
## ..$ host : chr "176.54.3.21"
## ..$ port : int 4321
## ..$ user : chr "user"
## ..$ pwd : int [1:4] 112 97 115 115
## ..$ options: chr "-c search_path=ANL"
## $ date : Date[1:1], format: "2020-05-01"
## $ seed : int 2020
## $ debug : logi TRUE
## $ comments : chr "Для среды ОБУЧЕНИЯ не задаватьте НИЧЕГО, это тоже, что дать опцию 'default' в Renviron.site / Rprofile.site, на"| __truncated__
## $ server : chr "localhost:7600"
## - attr(*, "config")= chr "default"
## - attr(*, "file")= chr "C:\\Users\\arodionov\\Documents\\Projects\\20.01_SocialPortraitOfAFamily\\plumber-api\\config.yml"
## The end of Specific Configuration Values.
Обратите внимание, что критически важную операцию загрузки config.yml файла вызывают в виде функции обработки ошибок в момент выполнения под именем try_Config_Get() для того, чтобы при необходимости перехватывать управление после некорректной попытки считать YAML файла с переданными опциями.
config.yml конфиденциальные данныеОчевидно, что конфиденциальные сведения важно хранить в зашифрованном виде, например, в шестнадцатиричном коде или закодированной по специальному ключю. Хотя порой такие данные хранятся как константы в конфигурационном файле .Renviron, но тогда они остаются неизменными во время всего сеанса R.
Для считывания пароля либо иной секретной информации нельзя применять какие-либо промежуточные переменные, поскольку содержимое таких переменных может быть перехвачено посторонними агентами. Поэтому такие сведения надлежит непосредственно брать из источника хранения.
# I. Show the Password from YAML File `config.yml` after converting from Hexadecimal codes
# paste0('0x', as.hexmode(utf8ToInt('password')), collapse = ', ') or by MS Excel ="0x" & DEC2HEX(CODE(A1)) & ","
writeLines('\n Convert password from comnfig.yml file of the Hexadecimal Codes:')##
## Convert password from comnfig.yml file of the Hexadecimal Codes:
## [1] "pass"
close( rawConnection(as.raw(config$datawarehouse$pwd)) ) # Close Raw Connection with a password
# II. Check the activation of a particular Configuration Set of Value (Section) 'default' from YAML File
writeLines("\n You can check whether a particular configuration 'default' is active using the `config::is_active()`:")##
## You can check whether a particular configuration 'default' is active using the `config::is_active()`:
## [1] TRUE
# III. Check the activation of a particular Configuration Set of Value (Section) 'test' from YAML File
writeLines("\n You can check whether a particular configuration 'test' is active using the `config::is_active()`:")##
## You can check whether a particular configuration 'test' is active using the `config::is_active()`:
## [1] FALSE
# IV. Check the activation of a particular Configuration Set of Value (Section) 'production' from YAML File
writeLines("\n You can check whether a particular configuration 'production' is active using the `config::is_active()`:")##
## You can check whether a particular configuration 'production' is active using the `config::is_active()`:
## [1] FALSE
# V. Read a single configuration value (will return 'debug' = FALSE from a particular Configuration Values 'production')
writeLines( sprintf('\n You can Read a single configuration value from a loaded Configuration Sets of Values:\n debug = %s ', suppressWarnings( config::get('debug') ) ) )##
## You can Read a single configuration value from a loaded Configuration Sets of Values:
## debug = TRUE
Очевидно, что из возможных вариантов наборов предустановленных значений (например, default, test и production) загружен только один - default, который предусматривается для среды ОБУЧЕНИЯ (разработки моделей).
Теперь проверим как работает набор конфигурационных значений на примере получения списка таблиц из базы данных PostgreSQL, к которой подключаются по параметрам, считанных из config.yml файла, приведенного выше. Будет использован набор предустановленных значений для Обучения т.е. из секции default YAML файла.
library('DBI') # R Database Interface
# install.packages('RPostgreSQL')
library('RPostgreSQL') # R Interface to the 'PostgreSQL' Database System
writeLines( sprintf('Current version of package for DBMS PostgreSQL: %s', packageVersion('RPostgreSQL')) )## Current version of package for DBMS PostgreSQL: 0.6.2
try_dbConnect_PostgreSQL <- function(drv) { # https://stackoverflow.com/questions/12193779/how-to-write-trycatch-in-r
out <-
tryCatch(
expr = {
con <- DBI::dbConnect(
drv = drv,
dbname = config[['datawarehouse']][['dbname']],
host = config[['datawarehouse']][['host']],
port = config[['datawarehouse']][['port']],
user = config[['datawarehouse']][['user']],
password = suppressWarnings( readLines(rawConnection(as.raw(config[['datawarehouse']][['pwd']]))) ), # rstudioapi::askForPassword('Password!')
options = config[['datawarehouse']][['options']]
)
close( rawConnection(as.raw(config[['datawarehouse']][['pwd']])) )
message('Successfully executed the DBI::dbConnect(x) call.')
return( con )
}, # end of `expr`
error = function(e){
close( rawConnection(as.raw(config[['datawarehouse']][['pwd']])) )
message('Unsuccessfully executed the DBI::dbConnect(x) call!!!')
print(e)
return( NULL )
}, # end of `error`
warning = function(w){
close( rawConnection(as.raw(config[['datawarehouse']][['pwd']])) )
message('Unsuccessfully executed the DBI::dbConnect(x) call!')
print(w)
return( con )
}, # end of `warning`
finally = {
message('All done in `dbConnect_PostgreSQL()`.')
} # end of `finally`
)
return( out )
} # End of function `try_dbConnect_PostgreSQL()`
try_dbListTables_PostgreSQL <- function(con) {
out <-
tryCatch(
expr = {
result <- DBI::dbListTables( con )
return( result )
}, # end of `expr`
error = function(e){
message('Unsuccessfully executed the DBI::dbListTables(con) call!!!')
print(e)
return( NULL )
}, # end of `error`
warning = function(w){
message('Unsuccessfully executed the DBI::dbListTables(con) call!')
print(w)
return( NULL )
}, # end of `warning`
finally = {
message('All done in `dbConnect_dbListTables()`.')
} # end of `finally`
)
return( out )
} # End of function `try_dbListTables_PostgreSQL()`
if ( exists('config') & is.null(config) == FALSE & config[['datawarehouse']][['driver']] == 'PostgreSQL' ) {
writeLines('\nTo test for whether a configuration «default» is active you should use the `config::is_active()` function:')
config::is_active('default')
drv <- DBI::dbDriver(config[['datawarehouse']][['driver']])
# Get all the tables from connection
writeLines('The Description of Driver for DBMS PostgreSQL:')
summary(drv)
con <- try_dbConnect_PostgreSQL( drv )
if ( is.null(con) == FALSE & RPostgreSQL::isPostgresqlIdCurrent(con) == TRUE ) { # Check Connection to PostgreSQL
writeLines('\nThe Description of Session for DBMS PostgreSQL:')
summary(con)
writeLines('\nThe Description of Meta-Data in DBMS PostgreSQL:')
out <- DBI::dbGetInfo(con)
print( out )
writeLines('\nThe Description of Data Type to DBMS PostgreSQL:')
out <- DBI::dbDataType(con, 'ANY')
print( out )
browser()
writeLines('\nThe Table list of Session`s User to DBMS PostgreSQL:')
out <- try_dbListTables_PostgreSQL( con )
print( out[grep('contracts_fpd_', out)] )
} else {
writeLines( sprintf('Unable to connect to Database `%s`!', config[['datawarehouse']][['dbname']]) )
} # end if ( is.null(con) == FALSE & RPostgreSQL::isPostgresqlIdCurrent(con) == TRUE )
}##
## To test for whether a configuration «default» is active you should use the `config::is_active()` function:
## The Description of Driver for DBMS PostgreSQL:
## <PostgreSQLDriver:(9664)>
## Driver name: PostgreSQL
## Max connections: 16
## Conn. processed: 0
## Default records per fetch: 500
## Open connections: 0
## Successfully executed the DBI::dbConnect(x) call.
## All done in `dbConnect_PostgreSQL()`.
##
## The Description of Session for DBMS PostgreSQL:
## <PostgreSQLConnection:(9664,0)>
## User: user
## Host: 176.54.3.21
## Dbname: gpdb
## No resultSet available
##
## The Description of Meta-Data in DBMS PostgreSQL:
## $host
## [1] "176.54.3.21"
##
## $port
## [1] "4321"
##
## $user
## [1] "user"
##
## $dbname
## [1] "gpdb"
##
## $serverVersion
## [1] "9.4.24"
##
## $protocolVersion
## [1] 3
##
## $backendPId
## [1] 16682
##
## $rsId
## list()
##
##
## The Description of Data Type to DBMS PostgreSQL:
## [1] "text"
## Called from: eval(expr, envir, enclos)
## debug на <text>#107: writeLines("\nThe Table list of Session`s User to DBMS PostgreSQL:")
##
## The Table list of Session`s User to DBMS PostgreSQL:
## debug на <text>#108: out <- try_dbListTables_PostgreSQL(con)
## All done in `dbConnect_dbListTables()`.
## debug на <text>#109: print(out[grep("contracts_fpd_", out)])
## [1] "contracts_fpd_spd_fiz" "contracts_fpd_spd_ipjur"
Обратите внимание, что критически важные операции открытия пользователю сеанса с Базой данных PostgreSQL и извлечения списка таблиц у пользователя во время этого сеанса вызываются в виде функций обработки ошибок в момент выполнения под именами try_dbConnect_PostgreSQL() и try_dbListTables_PostgreSQL() соответственно.
Теперь обратимся к таблице contracts_fpd_spd_fiz Базы данных и извлечем из нее небольшую выборку, где поле providerid имеет значения 321 или 320.
# Declare Subset of table
library('glue') # Interpreted String Literals
types <- c(320L, 321L)
Provider_Status <- glue::glue_sql('{types*}', .con = con)Следующий фрагмент объявлен как SQL chunck {sql ...}, поэтому необходимо указать в заголовке (то есть в фигурных скобках chunck) как connection = con, так и output.var = 'trials'. Кавычки обрамляющие выходную переменную trials также важны.
-- Query to PostgreSQL's Table with options
-- connection = 'con' in this chunk
-- output.var = 'trials' in this chunk
SELECT * FROM contracts_fpd_spd_fiz WHERE providerid IN (?Provider_Status) LIMIT 20;Кстати, если сеанс связи с Базой данных будет прерван, то выполнение вышеуказанного SQL кода выдаст ошибку:
Error in (function (sql, outputFile, options) :
The 'connection' option must be a valid DBI connection.
Failed to execute SQL chunk
Теперь опубликуем часть выборки, извлеченную из таблицы contracts_fpd_spd_fiz Базы данных и переданную в выходную переменную trials.
## creditinfoid contractid providerid startdate fpd spd
## 1 7944 3183 321 2019-12-12 1 NA
## 2 8135 3184 320 2019-12-12 NA 1
## 3 4908 9392 321 2020-01-29 NA 1
## 4 2293 9425 321 2020-01-29 1 NA
## 5 2779 1817 320 2019-12-13 1 NA
## 6 9468 8154 321 2019-12-15 1 NA
Наконец, ставим точку в этом сеансе связи с Базой данных и отключаем Драйвера Базы данных.
if ( exists('config') & is.null(config) == FALSE & config[['datawarehouse']][['driver']] == 'PostgreSQL' ) {
if ( is.null(con) == FALSE & RPostgreSQL::isPostgresqlIdCurrent(con) == TRUE ) { # Check Connection to PostgreSQL
# Close the connection
writeLines('\nClose the connection to DBMS PostgreSQL:')
out <- DBI::dbDisconnect(con)
print( out )
} # end if ( is.null(con) == FALSE & RPostgreSQL::isPostgresqlIdCurrent(con) == TRUE )
writeLines('Unload the Driver for DBMS PostgreSQL:')
DBI::dbUnloadDriver(drv)
} # end if ( config[['datawarehouse']][['driver']] == 'PostgreSQL')## debug на <text>#4: if (is.null(con) == FALSE & RPostgreSQL::isPostgresqlIdCurrent(con) ==
## TRUE) {
## writeLines("\nClose the connection to DBMS PostgreSQL:")
## out <- DBI::dbDisconnect(con)
## print(out)
## }
## debug на <text>#7: writeLines("\nClose the connection to DBMS PostgreSQL:")
##
## Close the connection to DBMS PostgreSQL:
## debug на <text>#8: out <- DBI::dbDisconnect(con)
## debug на <text>#9: print(out)
## [1] TRUE
## debug на <text>#13: writeLines("Unload the Driver for DBMS PostgreSQL:")
## Unload the Driver for DBMS PostgreSQL:
## debug на <text>#14: DBI::dbUnloadDriver(drv)
## [1] TRUE
## - Session info -------------------------------------------------------------------------------------------------------
## setting value
## version R version 4.0.2 (2020-06-22)
## os Windows 10 x64
## system x86_64, mingw32
## ui RTerm
## language (EN)
## collate Russian_Russia.1251
## ctype Russian_Russia.1251
## tz Asia/Dhaka
## date 2020-08-14
##
## - Packages -----------------------------------------------------------------------------------------------------------
## package * version date lib source
## assertthat 0.2.1 2019-03-21 [1] CRAN (R 4.0.0)
## backports 1.1.8 2020-06-17 [1] CRAN (R 4.0.2)
## callr 3.4.3 2020-03-28 [1] CRAN (R 4.0.0)
## cli 2.0.2 2020-02-28 [1] CRAN (R 4.0.0)
## config * 0.3 2018-03-27 [1] CRAN (R 4.0.0)
## crayon 1.3.4 2017-09-16 [1] CRAN (R 4.0.0)
## DBI * 1.1.0 2019-12-15 [1] CRAN (R 4.0.0)
## desc 1.2.0 2018-05-01 [1] CRAN (R 4.0.0)
## devtools 2.3.0 2020-04-10 [1] CRAN (R 4.0.0)
## digest 0.6.25 2020-02-23 [1] CRAN (R 4.0.0)
## ellipsis 0.3.1 2020-05-15 [1] CRAN (R 4.0.0)
## evaluate 0.14 2019-05-28 [1] CRAN (R 4.0.0)
## fansi 0.4.1 2020-01-08 [1] CRAN (R 4.0.0)
## fs 1.5.0 2020-07-31 [1] CRAN (R 4.0.2)
## glue * 1.4.1 2020-05-13 [1] CRAN (R 4.0.0)
## htmltools 0.5.0 2020-06-16 [1] CRAN (R 4.0.0)
## knitr 1.29 2020-06-23 [1] CRAN (R 4.0.2)
## magrittr 1.5 2014-11-22 [1] CRAN (R 4.0.0)
## memoise 1.1.0 2017-04-21 [1] CRAN (R 4.0.0)
## pkgbuild 1.1.0 2020-07-13 [1] CRAN (R 4.0.2)
## pkgload 1.1.0 2020-05-29 [1] CRAN (R 4.0.0)
## prettyunits 1.1.1 2020-01-24 [1] CRAN (R 4.0.0)
## processx 3.4.3 2020-07-05 [1] CRAN (R 4.0.2)
## ps 1.3.3 2020-05-08 [1] CRAN (R 4.0.0)
## R6 2.4.1 2019-11-12 [1] CRAN (R 4.0.0)
## remotes 2.1.1 2020-02-15 [1] CRAN (R 4.0.0)
## rlang 0.4.7 2020-07-09 [1] CRAN (R 4.0.2)
## rmarkdown 2.3 2020-06-18 [1] CRAN (R 4.0.0)
## RPostgreSQL * 0.6-2 2017-06-24 [1] CRAN (R 4.0.2)
## rprojroot 1.3-2 2018-01-03 [1] CRAN (R 4.0.0)
## sessioninfo 1.1.1 2018-11-05 [1] CRAN (R 4.0.0)
## stringi 1.4.6 2020-02-17 [1] CRAN (R 4.0.0)
## stringr 1.4.0 2019-02-10 [1] CRAN (R 4.0.0)
## testthat 2.3.2 2020-03-02 [1] CRAN (R 4.0.0)
## usethis 1.6.1 2020-04-29 [1] CRAN (R 4.0.0)
## withr 2.2.0 2020-04-20 [1] CRAN (R 4.0.0)
## xfun 0.16 2020-07-24 [1] CRAN (R 4.0.2)
## yaml 2.2.1 2020-02-01 [1] CRAN (R 4.0.0)
##
## [1] C:/R/R-4.0.2/library
## [1] "2020-08-14 13:07:05 +06"