4 from sqlalchemy
.exc
import IntegrityError
5 import sqlalchemy
.types
7 from .db
import connect
, metadata
, tables
as tables_module
8 from pokedex
.lookup
import lookup
as pokedex_lookup
11 if len(sys
.argv
) <= 1:
17 # Find the command as a function in this file
18 func
= globals().get("command_%s" % command
, None)
25 def command_csvimport(engine_uri
, directory
='.'):
28 from sqlalchemy
.orm
.attributes
import instrumentation_registry
30 session
= connect(engine_uri
)
34 # SQLAlchemy is retarded and there is no way for me to get a list of ORM
35 # classes besides to inspect the module they all happen to live in for
36 # things that look right.
37 table_base
= tables_module
.TableBase
38 orm_classes
= {} # table object => table class
40 for name
in dir(tables_module
):
41 # dir() returns strings! How /convenient/.
42 thingy
= getattr(tables_module
, name
)
44 if not isinstance(thingy
, type):
47 elif not issubclass(thingy
, table_base
):
48 # Not a declarative table; bail
50 elif thingy
== table_base
:
51 # Declarative table base, so not a real table; bail
54 # thingy is definitely a table class! Hallelujah.
55 orm_classes
[thingy
.__table__
] = thingy
57 # Okay, run through the tables and actually load the data now
58 for table_obj
in metadata
.sorted_tables
:
59 table_class
= orm_classes
[table_obj
]
60 table_name
= table_obj
.name
62 # Print the table name but leave the cursor in a fixed column
63 print table_name
+ '...', ' ' * (40 - len(table_name
)),
67 csvfile
= open("%s/%s.csv" %
(directory
, table_name
), 'rb')
69 # File doesn't exist; don't load anything!
73 reader
= csv
.reader(csvfile
, lineterminator
='\n')
74 column_names
= [unicode(column
) for column
in reader
.next()]
76 # Self-referential tables may contain rows with foreign keys of other
77 # rows in the same table that do not yet exist. Pull these out and add
78 # them to the session last
79 # ASSUMPTION: Self-referential tables have a single PK called "id"
80 deferred_rows
= [] # ( row referring to id, [foreign ids we need] )
81 seen_ids
= {} # primary key we've seen => 1
83 # Fetch foreign key columns that point at this table, if any
85 for column
in table_obj
.c
:
86 if any(_
.references(table_obj
) for _
in column
.foreign_keys
):
87 self_ref_columns
.append(column
)
92 for column_name
, value
in zip(column_names
, csvs
):
93 column
= table_obj
.c
[column_name
]
94 if column
.nullable
and value
== '':
95 # Empty string in a nullable column really means NULL
97 elif isinstance(column
.type, sqlalchemy
.types
.Boolean
):
98 # Boolean values are stored as string values 0/1, but both
99 # of those evaluate as true; SQLA wants True/False
105 # Otherwise, unflatten from bytes
106 value
= value
.decode('utf-8')
108 setattr(row
, column_name
, value
)
110 # May need to stash this row and add it later if it refers to a
111 # later row in this table
113 foreign_ids
= [getattr(row
, _
.name
) for _
in self_ref_columns
]
114 foreign_ids
= [_
for _
in foreign_ids
if _
] # remove NULL ids
117 # NULL key. Remember this row and add as usual.
120 elif all(_
in seen_ids
for _
in foreign_ids
):
121 # Non-NULL key we've already seen. Remember it and commit
122 # so we know the old row exists when we add the new one
127 # Non-NULL future id. Save this and insert it later!
128 deferred_rows
.append((row
, foreign_ids
))
135 # Attempt to add any spare rows we've collected
136 for row
, foreign_ids
in deferred_rows
:
137 if not all(_
in seen_ids
for _
in foreign_ids
):
138 # Could happen if row A refers to B which refers to C.
139 # This is ridiculous and doesn't happen in my data so far
140 raise ValueError("Too many levels of self-reference! "
141 "Row was: " + str(row
.__dict__
))
149 def command_csvexport(engine_uri
, directory
='.'):
151 session
= connect(engine_uri
)
153 for table_name
in sorted(metadata
.tables
.keys()):
155 table
= metadata
.tables
[table_name
]
157 writer
= csv
.writer(open("%s/%s.csv" %
(directory
, table_name
), 'wb'),
159 columns
= [col
.name
for col
in table
.columns
]
160 writer
.writerow(columns
)
162 primary_key
= table
.primary_key
163 for row
in session
.query(table
).order_by(*primary_key
).all():
166 # Convert Pythony values to something more universal
167 val
= getattr(row
, col
)
175 val
= unicode(val
).encode('utf-8')
179 writer
.writerow(csvs
)
181 def command_lookup(engine_uri
, name
):
182 # XXX don't require uri! somehow
183 session
= connect(engine_uri
)
185 results
, exact
= pokedex_lookup(session
, name
)
189 print "Fuzzy-matched:"
191 for object in results
:
192 print object.__tablename__
, object.name
196 print u
"""pokedex -- a command-line Pokédex interface
198 help Displays this message.
199 lookup {uri} [name] Look up something in the Pokédex.
201 These commands are only useful for developers:
202 csvimport {uri} [dir] Import data from a set of CSVs to the database
204 csvexport {uri} [dir] Export data from the database given by the URI
206 Directory defaults to cwd.
207 """.encode(sys
.getdefaultencoding(), 'replace')