PK Oobcp/__init__.py"""This is a python utility that allows users to import/export data to/from a database.""" __version__ = '0.2.1' name = 'bcp' from .core import BCP from .connections import Connection from .files import DataFile PKչ Oj  bcp/config.py""" This module creates directories (or returns existing directories) to store logs, data and other artifacts. .. note:: This application defaults to creating a 'bcp' directory inside of the %userprofile% directory. However, this can be overridden by setting a value for the environmental variable BCP_ROOT_DIR. The structure within this root directory will always be the same. """ from pathlib import Path import os def get_bcp_root_dir() -> Path: user_profile = Path(os.environ.get('USERPROFILE', '')) bcp_root_dir_default = user_profile / Path('bcp') bcp_root_dir = Path(os.environ.get('BCP_ROOT_DIR', bcp_root_dir_default.absolute())) return bcp_root_dir BCP_ROOT_DIR = get_bcp_root_dir() BCP_DATA_DIR = BCP_ROOT_DIR / Path('data') BCP_LOGGING_DIR = BCP_ROOT_DIR / Path('logs') BCP_ROOT_DIR.mkdir(parents=True, exist_ok=True) BCP_DATA_DIR.mkdir(parents=True, exist_ok=True) BCP_LOGGING_DIR.mkdir(parents=True, exist_ok=True) PK OW3xxbcp/connections.py""" This module contains data structures required to connect to a database. While Auth can be instantiated on its own, you would generally create it at the same time as the connection. Since the Auth object will contain credentials, it's recommended to set these through secure means, such as environmental variables, so as to not accidentally check in your credentials to your source control. Even your host name can be considered sensitive data, depending on your and your company's policies. Example: .. code-block:: python import os from bcp import Connection, BCP host = os.environ['HOST'] username = os.environ['USERNAME'] password = os.environ['PASSWORD'] conn = Connection(host, 'mssql', username, password) my_bcp = BCP(conn) """ from .exceptions import DriverNotSupportedException, InvalidCredentialException class Auth: """ This data structure collects the username and password as an authentication object. Args: username: username for the authorization password: password for the authorization """ def __init__(self, username: str = None, password: str = None): self.username = username self.password = password assert self.type @property def type(self) -> str: """ This property identifies the authorization type depending on the username/password provided. The two options for authorization are Trusted and Credential. A Trusted connection is created when no username and no password are provided. In this case, the local user's credentials and authorization method are used. A Credential connection is created when both a username and a password are provided. If only one of username and password are provided, this raises an InvalidCredentialException. Returns: the type of connection ('Trusted' or 'Credential') """ if self.username is None and self.password is None: return 'Trusted' elif self.username is not None and self.password is not None: return 'Credential' else: raise InvalidCredentialException def __repr__(self): return f'' class Connection: """ This data structure describes a connection to be used to instantiate a BCP instance. A host and driver must be supplied. A username/password combination can also be supplied upon instantiation to automatically create an associated Auth object. Alternatively, this can be set as an attribute after instantiation. If the username/password are not provided, the connection will assume a Trusted authorization in the meantime. Args: host: the host where the database exists driver: the type of database (mssql, etc.) username: the username for authentication password: the password for authentication """ def __init__(self, host: str, driver: str, username: str = None, password: str = None): self.host = host self.auth = Auth(username, password) self.driver = driver @property def driver(self) -> str: return self._driver @driver.setter def driver(self, value: str = None): if value not in ['mssql']: raise DriverNotSupportedException self._driver = value def __repr__(self): """ This differs from __str__() because we don't want tracebacks to accidentally display credentials in plain text. """ return f'' def __str__(self): """ This will generate a BCP formatted connection string in the dialect of the associated database. Returns: a BCP formatted, dialect-specific, connection string """ if self.driver == 'mssql': if self.auth.type == 'Trusted': auth_string = f'-T' else: auth_string = f'-U {self.auth.username} -P {self.auth.password}' return f'-S {self.host} {auth_string}' PK OU bcp/core.py""" This is the core module of the library, containing the primary interface over the functionality of bcp. Along with its dependencies on Connection and DataFile, it serves as the entry point to the library. Simply create a Connection, pass it in to the BCP object, and then use load() or dump() to read data into and out of a database. See the methods below for examples. """ from .exceptions import DriverNotSupportedException from .connections import Connection from .files import DataFile from .dialects import mssql class BCP: """ This is the interface over dialect-specific classes that provides generic methods to load/dump data to/from a database. Args: connection: a Connection object that contains authorization details and database details Example: .. code-block:: python from bcp import BCP, Connection, DataFile conn = Connection('host', 'mssql', 'username', 'password') my_bcp = BCP(conn) """ def __init__(self, connection: Connection): self.connection = connection def load(self, input_file: DataFile, table: str) -> str: """ This method provides an interface to the lower level dialect-specific BCP load classes. Args: input_file: the file to be loaded into the database table: the table in which to land the data Returns: the name of the table that contains the data Example: .. code-block:: python from bcp import BCP, Connection, DataFile conn = Connection(host='HOST', driver='mssql', username='USER', password='PASSWORD') my_bcp = BCP(conn) file = DataFile(file_path='path/to/file.csv', delimiter=',') my_bcp.load(file, 'table_name') """ if self.connection.driver == 'mssql': load = mssql.MSSQLLoad(self.connection, input_file, table) else: raise DriverNotSupportedException return load.execute() def dump(self, query: str, output_file: DataFile = None) -> DataFile: """ This method provides an interface to the lower level dialect-specific BCP dump classes. Args: query: the query whose results should be saved off to a file output_file: the file to which the data should be saved, if no file is provided, one will be created in the BCP_DATA_DIR Returns: the data file object, which is useful when it is defaulted Example: .. code-block:: python from bcp import BCP, Connection conn = Connection(host='HOST', driver='mssql', username='USER', password='PASSWORD') my_bcp = BCP(conn) file = my_bcp.dump('select * from sys.tables') print(file) # %USERPROFILE%/bcp/data/.tsv """ if self.connection.driver == 'mssql': dump = mssql.MSSQLDump(self.connection, query, output_file) else: raise DriverNotSupportedException return dump.execute() def __repr__(self): return f'' PK Obcp/exceptions.py"""This module contains exceptions for this application.""" class DriverNotSupportedException(Exception): """This exception occurs when an unsupported driver is provided to a Connection or BCP object""" pass class InvalidCredentialException(Exception): """This exception occurs when a username is provided without a password, or vice versa, for an Auth or BCP object""" pass PK O)ՈtN N bcp/files.py""" This module contains data structures required to create and access files. Users will generally only need to use DataFile directly. LogFile and ErrorFile are used indirectly by the BCP classes. Example: .. code-block:: python from bcp import DataFile # create a csv to write out my data my_file = DataFile(delimiter=',') print(my_file.path) # %USERPROFILE%/bcp/data/.csv """ import abc import datetime from pathlib import Path from .config import BCP_LOGGING_DIR, BCP_DATA_DIR class File(abc.ABC): """ This data structure creates a file handle given a file path. If the file path is not provided: - the current timestamp is used so that unique error, log, and data files can be created - the file will be created in the BCP_ROOT_DIR directory specified in config.py """ _default_extension = None _default_directory = None @property def file(self) -> Path: return self._file @file.setter def file(self, value: Path = None): """ This method generates a default file path object if none is provided Returns: a Path object that points to the file """ if value is not None: self._file = value else: timestamp_format: str = '%Y_%m_%d_%H_%M_%S_%f' timestamp = datetime.datetime.now() file_name: str = '.'.join([timestamp.strftime(timestamp_format), self._default_extension]) self._file = self._default_directory / Path(file_name) @property def path(self) -> Path: return self.file.absolute() class DataFile(File): """ This is a handle to a data file. Args: file_path: the path object to the file, if not provided, a default using the current timestamp will be created delimiter: the field delimiter for the data file """ def __init__(self, file_path: Path = None, delimiter: str = None): self._default_directory = BCP_DATA_DIR self.delimiter = delimiter or '\t' if self.delimiter == '\t': self._default_extension = 'tsv' elif self.delimiter == ',': self._default_extension = 'csv' else: self._default_extension = 'dat' self.file = file_path class LogFile(File): """ This is a handle to a log file. Args: file_path: the path object to the file, if not provided, a default using the current timestamp will be created """ def __init__(self, file_path: Path = None): self._default_directory = BCP_LOGGING_DIR self._default_extension = 'log' self.file = file_path class ErrorFile(File): """ This is a handle to an error file. Args: file_path: the path object to the file, if not provided, a default using the current timestamp will be created """ def __init__(self, file_path: Path = None): self._default_directory = BCP_DATA_DIR self._default_extension = 'err' self.file = file_path PK ON>  bcp/dialects/base.pyimport abc from ..files import DataFile from ..connections import Connection class BCPLoad(abc.ABC): """ This is the abstract base class for all dialect-specific Dump implementations. It contains required methods and default values for all subclasses. Args: connection: the Connection object that points to the database from which we want to export data file: the file to be loaded into the database table: the table in which to land the data """ def __init__(self, connection: Connection, file: DataFile, table: str): self.connection = connection self.file = file self.table = table @abc.abstractmethod def execute(self) -> str: """ This will execute the the data import process. Returns: the name of the table that contains the data """ raise NotImplementedError class BCPDump(abc.ABC): """ This is the abstract base class for all driver specific Dump implementations. It contains required methods and default values for all subclasses. Args: connection: the Connection object that points to the database from which we want to export data query: the query for the data to be exported file: the file to write the to, if not provided, a default will be created in the BCP_DATA_DIR """ def __init__(self, connection: Connection, query: str, file: DataFile = None): self.connection = connection self.query = query self.file = file @abc.abstractmethod def execute(self) -> DataFile: """ This will execute the data export process Returns: the data file object, which is useful when it is defaulted """ raise NotImplementedError @property def file(self) -> DataFile: return self._file @file.setter def file(self, value: DataFile = None): """ This setter will create a default file in the BCP_DATA_DIR if no file is provided. Args: value: the file to use instead of the default, if provided """ if value is None: self._file = DataFile() else: self._file = value PK OnI\\bcp/dialects/mssql.py""" This module contains MS SQL Server specific logic that works with the BCP command line utility. None of these classes are relevant beyond the scope of this library, and should not be used outside of the library. """ import subprocess from ..exceptions import DriverNotSupportedException from ..connections import Connection from ..files import LogFile, ErrorFile, DataFile from .base import BCPLoad, BCPDump class MSSQLBCP: """This mixin provides properties for MSSQL's BCP. It serves as a mixin for BCPLoad and BCPDump subclasses below.""" file = None batch_size = None @property def command(self) -> str: """ This allows you to see the command that will be executed via bcp without executing the command, similar to how sqlalchemy can show you the generated query or execute the generated query. This makes debugging a little easier. Returns: the command that will be passed into the bcp command line utility """ raise NotImplementedError @property def config(self) -> str: """ This method will generate a configuration string. It supports the delimiter option. Returns: a BCP formatted configuration string """ return f'-c -t "{self.file.delimiter}"' @property def logging(self) -> str: """ This method will generate a log file string that will write the log file to the BCP_LOGGING_DIR directory. Returns: a BCP formatted log file string """ log_file = LogFile() return f'-o "{log_file.path}"' class MSSQLLoad(MSSQLBCP, BCPLoad): """ This class is the MS SQL Server implementation of the BCP Load operation. Args: connection: the (mssql) Connection object that points to the database from which we want to export data file: the file whose data should be imported into the target database table: the into which the data will be written """ def __init__(self, connection: Connection, file: DataFile, table: str): if connection.driver != 'mssql': raise DriverNotSupportedException super().__init__(connection, file, table) self.batch_size = 10000 def execute(self) -> str: """ This will run the instance's command via the BCP utility Returns: the name of the table that contains the data """ subprocess.run(f'bcp {self.command}', check=True) return self.table @property def command(self) -> str: """ This method will build the command that will import data from the supplied file to the supplied table. Returns: the command that will be passed into the BCP command line utility """ return f'{self.table} in "{self.file.path}" {self.connection} {self.config} {self.logging} {self.error}' @property def config(self) -> str: """ This method will generate a configuration string. It supports the delimiter and batch size options. Returns: a BCP formatted configuration string """ return f'{super().config} -b {self.batch_size}' @property def error(self) -> str: """ This method will generate an error file string that will write the error file to the BCP_DATA_DIR directory. Returns: a BCP formatted error file string """ error_file = ErrorFile() return f'-e "{error_file.path}"' class MSSQLDump(MSSQLBCP, BCPDump): """ This class is the MS SQL Server implementation of the BCP Dump operation. Args: connection: the Connection object that points to the database from which we want to export data query: the query defining the data to be exported file: the file to which the data will be written """ def __init__(self, connection: Connection, query: str, file: DataFile = None): if connection.driver != 'mssql': raise DriverNotSupportedException super().__init__(connection, query, file) def execute(self) -> DataFile: """ This will run the instance's command via the BCP utility Returns: the data file object that contains the exported data """ subprocess.run(f'bcp {self.command}', check=True) return self.file @property def command(self) -> str: """ This method will build the command that will export data from the supplied table to the supplied file. Returns: the command that will be passed into the BCP command line utility """ return f'"{self.query}" queryout "{self.file.path}" {self.connection} {self.config} {self.logging}' PK O_hhbcp-0.2.1.dist-info/LICENSEMIT License Copyright (c) 2019 Michael R. Alfare, on behalf of Markel Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HPObcp-0.2.1.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UrPK!HY+Y# bcp-0.2.1.dist-info/METADATAV]o6}篸hQ8,i]=xuIРZ -][D$%);w$ibŀMb{Q'c P sN`ֵ ]&4M,mCm4ʮ-Y3| ˹w%r10ɲe;Os[ga]-LO>Y[_ y{N?gWdjL?D.̰pM5>KYIBu[9$]z=}9޸Zr$mMa׍z[0{Mc,[I˯lvD{yB'G/M 4o^tJO ?{D8~*\?CC+Y,GMO^ͷye 8=TꩨDW4ŒMURɞRwwJDҟA+kD{tyҒvsm Q4N"E4|C[w Jmih4jeR.BpCn_ct 5:WX\meI&RzqhTK "WK c$,eNJ=CT]qC* _#|qvA6zg`fۋs:r #@ d]!hm1XH 'P+0"=ulHP@Q`[Xkƕic^dBKvȄSK4<\LxUDž=v9E-sKZ&/%)[yL|Y;DjjvpMmSJb *$h}yD&BHaYYLRz ۼ0pGokчhaXG/ܩIe"BZ`{q! ,Z[y%is'GfKR>RJ4oMUHXRWkYC5cKoq;|yeB)WVgmbEBC9d-:c63Z)a奎RzuߏY:Q}7nxXpKkВR9vip!NGf1cOGuXk0)P?6ffOg\I$ޑ ϕ(;r:ӑM4+I˕Mx@8QCW\PK9iMH"`59Sz&/3)4{%8j:a3[嫾Wp 9{4j&Qu#Efh1d̔ PK!HEh8Zbcp-0.2.1.dist-info/RECORDm͎@< TJ(" !(BAѠ<1q&rιQL?0/6 N)4&ى'*&WUEE/@iwU7dDko[Z;%|]km]<>!m ZQls$2ۼ*ٛ:7c@>ʨ#eFp@mHxd:CRu9_bĥ 2)exW4f> k瀢gGQGyQ׹ JYǑ8uI6 ?elȧpPMfk[_PK Oobcp/__init__.pyPKչ Oj  bcp/config.pyPK OW3xxbcp/connections.pyPK OU bcp/core.pyPK O"bcp/exceptions.pyPK O)ՈtN N a$bcp/files.pyPK ON>  0bcp/dialects/base.pyPK OnI\\:bcp/dialects/mssql.pyPK O_hhMbcp-0.2.1.dist-info/LICENSEPK!HPOLRbcp-0.2.1.dist-info/WHEELPK!HY+Y# Rbcp-0.2.1.dist-info/METADATAPK!HEh8Z0Xbcp-0.2.1.dist-info/RECORDPK Z