-
Notifications
You must be signed in to change notification settings - Fork 3
Sql interface lfs #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
16fd555
290a28d
670fc95
6e3a3cb
bac7f3a
71e6793
80497e1
ba013eb
521530a
6c81eeb
0444c94
8d008f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # slac_db/yaml/* linguist-generated | ||
| # slac_db/package_data/* linguist-generated | ||
| # tests/test_data/* linguist-generated | ||
| # tests/test_data/lcls-tools-yaml/* linguist-generated | ||
| slac_db/yaml/* linguist-generated | ||
| slac_db/package_data/* linguist-generated | ||
| tests/test_data/* linguist-generated | ||
| *.sqlite3 filter=lfs diff=lfs merge=lfs -text |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import slac_db.create.meme_names | ||
| import slac_db.create.lcls_elements_csv | ||
|
|
||
| def oracle_db(csv_source=None): | ||
| slac_db.create.lcls_elements_csv.to_oracle_db(csv_source) | ||
|
|
||
| def aida_db(): | ||
| slac_db.create.meme_names.to_aida_db() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import csv | ||
| import slac_db.config | ||
| import slac_db.oracle | ||
|
|
||
| def to_oracle_db(csv_source=None): | ||
| p = _Parser(csv_source=csv_source) | ||
| return slac_db.oracle.recreate(p) | ||
|
|
||
| class _Parser(): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the parentheses here to suggest that this takes an argument? |
||
| def __init__(self, csv_source=None): | ||
| if not csv_source: | ||
| csv_source = ( | ||
| slac_db.config.package_data() / "lcls_elements.csv" | ||
| ) | ||
| self.rows = {} | ||
| with open(csv_source, "r") as c: | ||
| reader = csv.reader(c) | ||
| self._parse_csv(reader) | ||
|
|
||
| def _parse_csv(self, reader): | ||
| names = [r.lower() for r in next(reader)] | ||
| i = 0 | ||
| for row in reader: | ||
| values = [None if v == '' else v for v in row] | ||
| self.rows[i] = dict(zip(names, values)) | ||
| i += 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import slac_db.directory_service | ||
|
|
||
| def to_directory_service_db(): | ||
| return slac_db.directory_service.recreate(_Parser()) | ||
|
|
||
| class _Parser: | ||
| def __init__(self): | ||
| self.addresses = set() | ||
| self._get_from_meme() | ||
|
|
||
| def _get_from_meme(self): | ||
| import meme.names | ||
|
nneveu marked this conversation as resolved.
|
||
| address_list = meme.names.list_pvs("%", timeout=600) | ||
| for a in address_list: | ||
| self.addresses.add(a) | ||
|
eloise-nebula marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import os | ||
| import os.path | ||
| import slac_db.config | ||
| import sqlalchemy | ||
| import pykern.sql_db | ||
| import slac_db.oracle | ||
|
|
||
| _meta = None | ||
|
|
||
| def get_addresses(device=None): | ||
| """Get all addresses per device. | ||
|
|
||
| Args: | ||
| device (str): MAD name of the device as found in Oracle. | ||
|
|
||
| Returns: | ||
| tuple: Sorted address values. | ||
| """ | ||
| head = slac_db.oracle.get_address_header(device=device) | ||
| with _session() as s: | ||
| cs_address = s.t.addresses.c["address"] | ||
| return tuple(sorted( | ||
| r["address"] for r in s.select( | ||
| sqlalchemy.select( | ||
| cs_address | ||
| ).where( | ||
| cs_address.like(f"{head}%") | ||
| ) | ||
| ) | ||
| )) | ||
|
|
||
| def recreate(parser): | ||
| """Rebuild the local directory_service sqlite3 database | ||
| only if it is not already loaded. | ||
|
|
||
| Args: | ||
| parser: Container for column data. | ||
| """ | ||
| assert not _meta | ||
| assert parser.addresses | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these assert statements here for debugging? If not, I think it's better to not use assert statements for these kinds of checks.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the discussion in the workshop, should these be AssertError instead?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from slicops already
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eloise will add an error message |
||
| if os.path.exists(_directory_service_uri()): | ||
| os.remove(_directory_service_uri()) | ||
| _Inserter(parser) | ||
|
|
||
| class _Inserter: | ||
| """Creates a session and commits rows to the db. | ||
|
|
||
| Functions: | ||
| _addresses: Inserts all addresses in parser.addresses. | ||
| """ | ||
| def __init__(self, parser): | ||
| self.counts = {"addresses": 0} | ||
| with _session() as s: | ||
| self._addresses(parser.addresses, s) | ||
|
|
||
| def _addresses(self, addresses, session): | ||
| # We have to do it this way unfortunately. | ||
| # Bulk insert is not faster. | ||
| n = len(addresses) | ||
| i = 0 | ||
| for a in addresses: | ||
| session.insert("addresses", address=a) | ||
| i += 1 | ||
| print("{i} / {n}", end='\r') | ||
|
|
||
| def _db_type_prefix(uri): | ||
| if not uri.startswith("sqlite"): | ||
| uri = 'sqlite:///' + uri | ||
| return uri | ||
|
|
||
| def _init_db(uri=None): | ||
| """Initializes pykern sqlalchemy wrapper. Initialization | ||
| occurs when a session is first created. | ||
|
|
||
| _meta: wrapper that holds sqlalchemy metadata. | ||
| """ | ||
| global _meta | ||
| if uri is None: | ||
| uri = _directory_service_uri() | ||
| uri = _db_type_prefix(uri) | ||
| schema = { | ||
| "addresses": { | ||
| "address": "str 64 primary_key", | ||
| } | ||
| } | ||
| _meta = pykern.sql_db.Meta( | ||
| uri=uri, | ||
| schema=schema | ||
| ) | ||
|
|
||
| def _directory_service_uri(): | ||
| uri = ( | ||
| slac_db.config.package_data() / 'directory_service_pvs.sqlite3' | ||
| ) | ||
| return str(uri) | ||
|
|
||
| def _session(): | ||
| if _meta is None: | ||
| _init_db() | ||
| return _meta.session() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| import slac_db.config | ||
| import sqlalchemy | ||
| import pykern.sql_db | ||
| import os.path | ||
| import os | ||
|
|
||
|
|
||
| _meta = None | ||
|
|
||
| def get_address_header(device=None): | ||
| """Get address header of a device. | ||
|
|
||
| Args: | ||
| device (str): MAD name of the device as found in Oracle. | ||
|
|
||
| Returns: | ||
| tuple: The address header. | ||
| """ | ||
| with _session() as s: | ||
| return s.select_one( | ||
| sqlalchemy.select( | ||
| s.t.elements.c["control system name"] | ||
| ).where( | ||
| s.t.elements.c["element"] == device | ||
| ) | ||
| )["control system name"] | ||
|
|
||
| def get_devices(area=None, device_type=None): | ||
| """Get devices of one type from an area. | ||
|
|
||
| Args: | ||
| area (str): Name of the accelerator area. | ||
| device_type (str): Type of device as listed in Oracle. | ||
|
|
||
| Returns: | ||
| tuple: Device names in Z order. | ||
| """ | ||
| if device_type is None: | ||
| device_type = "%" | ||
| with _session() as s: | ||
| return tuple( | ||
| r.element for r in s.select( | ||
| sqlalchemy.select( | ||
| s.t.elements.c["element"] | ||
| ).where( | ||
| s.t.elements.c["keyword"] == device_type | ||
| ).where( | ||
| s.t.elements.c["area"] == area | ||
| ).order_by(s.t.elements.c["suml (m)"]) | ||
| ) | ||
| ) | ||
|
|
||
| def get_device_row(element=None): | ||
| """Get the full row for an element. | ||
|
|
||
| Args: | ||
| element: name of the element to get. | ||
| Returns: | ||
| sql alchemy row: Row object with each column. | ||
| """ | ||
| with _session() as s: | ||
| return s.select_one( | ||
| sqlalchemy.select( | ||
| s.t.elements | ||
| ).where( | ||
| s.t.elements.c["element"] == element | ||
| ) | ||
| ) | ||
|
|
||
| def get_beampaths(): | ||
| """Get all beampaths from Oracle. | ||
|
|
||
| Returns: | ||
| List of beampaths sorted alphabetically. | ||
| """ | ||
| beampaths = set() | ||
| def parse_beampaths(beampath_csv): | ||
| if beampath_csv is None: | ||
| return | ||
| c = beampath_csv.replace(' ', '').split(',') | ||
| c = filter(None, c) | ||
| beampaths.update(c) | ||
|
|
||
| with _session() as s: | ||
| query = sqlalchemy.select(s.t.elements.c.beampath).distinct() | ||
| for r in s.select(query): | ||
| parse_beampaths(r.beampath) | ||
| return sorted(list(beampaths)) | ||
|
|
||
|
|
||
| def get_areas(): | ||
| """Get all areas from Oracle. | ||
|
|
||
| Returns: | ||
| List of areas sorted alphabetically. | ||
| """ | ||
| def exclude_bad_patterns(column): | ||
| bad_patterns = ['\t- NO AREA -', '*%'] | ||
| filters = [None] * len(bad_patterns) | ||
| for i in range(0, len(bad_patterns)): | ||
| filters[i] = column.not_like(bad_patterns[i]) | ||
| return sqlalchemy.and_(*filters) | ||
|
|
||
| with _session() as s: | ||
| return list( | ||
| r.area for r in s.select( | ||
| sqlalchemy.select( | ||
| s.t.elements.c.area | ||
| ).where( | ||
| exclude_bad_patterns(s.t.elements.c.area) | ||
| ).distinct() | ||
| ) | ||
| ) | ||
|
|
||
| def recreate(parser): | ||
| """Rebuilds the sqlite copy of Oracle. | ||
| Fails if a connection has already been made. | ||
|
|
||
| Args: | ||
| Parser object with attribute 'rows' for row data. | ||
| """ | ||
| if _meta: | ||
| raise AssertionError( | ||
| "Database connnection already initialized. " | ||
| + "Restart Python interpreter." | ||
| ) | ||
| if not hasattr(parser, "rows"): | ||
| raise AssertionError( | ||
| "Parser is missing attribute 'rows'. " | ||
| ) | ||
| if os.path.exists(_oracle_uri()): | ||
| os.remove(_oracle_uri()) | ||
| _Inserter(parser) | ||
|
|
||
|
|
||
| class _Inserter: | ||
| """Inserts rows into sqllite database. | ||
| """ | ||
| def __init__(self, parser): | ||
| with _session() as s: | ||
| self._rows(parser.rows, s) | ||
| def _rows(self, rows, session): | ||
| for r in rows.values(): | ||
| ins = {} | ||
| for c in session.t.elements.c: | ||
| ins[c.name] = r[c.name] | ||
| session.insert("elements", **ins) | ||
|
|
||
|
|
||
| def _db_type_prefix(uri): | ||
| if not uri.startswith("sqlite"): | ||
| uri = 'sqlite:///' + uri | ||
| return uri | ||
|
|
||
| def _init_db(uri=None): | ||
| """Initializes pykern sqlalchemy wrapper. Initialization | ||
| occurs when a session is first created. | ||
|
|
||
| _meta: wrapper that holds sqlalchemy metadata. | ||
| """ | ||
| global _meta | ||
| if uri is None: | ||
| uri = _oracle_uri() | ||
| uri = _db_type_prefix(uri) | ||
| schema = { | ||
| "elements": { | ||
| "Area": "str 64 nullable", | ||
| "Element": "str 64 primary_key", | ||
| "Control System Name": "str 64 nullable", | ||
| "Keyword": "str 64 nullable", | ||
| "Beampath": "str 64 nullable", | ||
| "SumL (m)": "float 64 nullable", | ||
| "Effective Length (m)": "float 64 nullable", | ||
| "Rf Frequency (MHz)": "float 64 nullable" | ||
| } | ||
| } | ||
| _meta = pykern.sql_db.Meta( | ||
| uri=uri, | ||
| schema=schema | ||
| ) | ||
|
|
||
| def _oracle_uri(): | ||
| uri = ( | ||
| slac_db.config.package_data() / 'lcls_elements.sqlite3' | ||
| ) | ||
| return str(uri) | ||
|
|
||
| def _session(): | ||
| if _meta is None: | ||
| _init_db() | ||
| return _meta.session() |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some docstrings would be helpful to indicate that this function is where the Oracle db is made, and defaults to lcls_elements.csv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe name to_sqlite_db?