DC Zope/Python User Group
January 16, 2002
A.M. Kuchling
www.amk.ca
amk @ amk.ca
MEMS = MicroElectroMechanical Systems
They combine perception, computation, and actuation.
Inertial Measurement | Accelerometers, gyroscopes, vibration sensors |
Microfluidics | Gene chips, lab on a chip, chemical sensors, flow controllers, microvalves, micronozzles |
Optical MEMS | Optical switches, displays, adative optics |
Pressure Measurement | Auto sensors, medical sensors, industrial sensors |
RF Technology | RF switches, filters, variable capacitors, inductors, antennas, phase shifters, scanned aperatures |
Other | Data storage, picosatellites, actuators, etc. |
IC
Processes Oxidation Diffusion LPCVD Photolithography Epitaxy Sputtering Ion Implantation Etc. |
Micromachining
Processes Bulk Micromachining Surface Micromachining Wafer Bonding LIGA Deep Silicon Reactive Ion Etching Micromolding Etc. |
The MEMS Exchange will perform a process sequence in order to create a device. The process sequence can be spread across several individual fabrication facilities for the sake of process and design freedom.
The MEMS Exchange virtual fabrication network allows flexibility in:
Academic
Commercial:
First step toward building Web-based tools.
Checks a sequence for violations of several different rules.
Also graphically displays the layers being built up.
ProcessLibrary <<database root BaseProcess Parameter PhysicalValue ProcessModule ... UserDatabase <<database root User Group RunDatabase <<database root ProcessRun ProcessSequence [ProcessStep1, ...] Parameter TemplateLibrary, BusinessDatabase, MaterialDatabase, ...
Instead, we use the ZODB. Our database has:
Features:
Note: ACID = Atomicity, Consistency, Isolation, Durability
from ZODB import DB from ZODB.FileStorage import FileStorage storage = FileStorage('/tmp/test-filestorage.fs') db = DB(storage) conn = db.open() root = db.root() user_db = root['user_db'] newuser = User('amk') userdb.users['amk'] = newuser get_transaction().commit()
Replace FileStorage
with
BerkeleyStorage
to use a different low-level storage
mechanism.
ZEO consists of a ClientStorage
class which
retrieves objects over a socket, and a server that the
ClientStorage
can talk to.
That's it! There are a few rules to keep in mind, though...
import ZODB from Persistence import Persistent class User(Persistent): def __init__ (self): self.email = None self.names = [] def add_name (self, name): self.names.append(name) self._p_changed = 1
_p_changed
)isinstance()
, issubclass()
don't
work.__cmp__
, __r*__
, ...)__setattr__
, __delattr__
must set
dirty bit manuallyAn updated version for Python 2.2 should make #2 and #3 go away (eventually).
<User at 0000000000000ef3: akuchlin> prefix/first/last/suffix: Mr/Andrew/Kuchling/ email: 'amk@amk.ca' address: <Address at 0000000000000f2d> ... _p_changed: false
<Address at 0000000000000f2d> street1/2/3: 1320 N. Veitch St., #608// city/state/zip: Arlington/VA/22201 ... _p_changed: false
ludwig akuchlin>opendb root databases available: template_lib process_lib run_db user_db business_db results_db session_manager shared_cache other variables and functions: database connection root commit() = get_transaction().commit() abort() = get_transaction().abort() sync() = connection.sync() >>> r = run_db.get_run(113) >>> r.owner <User at 839e2f8: akuchlin> >>> r.owner = user_db.get_user('gward') >>> r.owner <User at 83a0348: gward> >>> commit() # commit the transaction
class ProcessRun(MXPersistent): """ Instance attributes: run_id : int the unique identifier for this process run name : string the user-supplied name of this run owner : User # notnone the individual user who owns this run billed : DateTime | boolean whether or not we've sent a bill to the customer for this run review_comments : PersistentList [Comment] An always-growing list of comments... """
Type-checking discovered database errors; here they are: run_db.runs[639].object_versions[6].sequence._key_map['S0002'].wafer_ids['W001'] .description.resistivity.unit.dims: expected attribute 'dimension_names' not found run_db.runs[639].object_versions[6].sequence._key_map['S0002'].wafer_ids['W001'] .description.resistivity.unit.dims: expected attribute 'num_dimensions' not found run_db.runs[639].object_versions[6].sequence._key_map['S0002'].wafer_ids['W001'] .description.resistivity.unit.dims: expected attribute 'dimension_powers' not found make: *** [check] Error 1
ute tools>MX_DB=file:/www/var/mxdb.fs python zodb_census.py opening database... expecting to see 306153 objects maximum expected OID: 00000000000abe43 OID: 00000000000abe44 (objects seen: 306153) census completed total OIDs attempted: 704068 empty slots seen: 397915 actual objects seen: 306153 objects seen by type: ActiveVersionCollection 963 Address 1861 AlignmentMark 51 BaseProcess 2324 BusinessDatabase 1 ... OOBTree 15 OOBucket 296 Parameter 29585 ParameterList 17966 ProcessHierarchy 1 ProcessLibrary 1 ProcessModule 840 ProcessRun 1885 ProcessSequence 1885 ProcessStep 16990 RunDatabase 1 ...
Builds an index of the references in the ZODB's object graph, and lets you explore it.
(Finding a bug, as demonstrated by Greg Ward on our internal mailing list)
First, a mystery:
$ opendb [...] >>> len(user_db.users) 775 $ make census [...] User 873
There are almost 100 more User objects in the ZODB than users
in our UserDatabase
object.
Let's list those 874 objects, sorted by user ID:
$ ./tools/zodb_index.py -C mems.access.user.User | sort -f -b +4 [...] 00000000000380d0: <User at 83805c8: akuchlin> 00000000000010ca: <User at 83278d8: gward> 0000000000000011: <User at 816cd18: Hexon> 000000000001a5d1: <User at 8332608: Hexon> 000000000001e4b1: <User at 833a138: Hexon> 0000000000021943: <User at 83437c0: Hexon> 0000000000023c4c: <User at 8346be8: Hexon> 00000000000317af: <User at 82c0578: Hexon> 00000000000010c9: <User at 83258d0: wbenard>
6 objects for the user "Hexon"?!?
Running zodb_index.py -r 0000000000000011
shows
251 refs to OID 11, so we'll guess that OID 11 is The One True
User Object for Hexon, and the others are impostors.
Let's dig deeper...
$ ./tools/zodb_index.py -r 1a5d1 1e4b1 21943 23c4c 317af 000000000001a5d1: <User at 81e4768: Hexon> 000000000001a5d0: <Review at 8220698> 000000000001a5d2: <FabProvider at 82248d0: UMich> 000000000001a5d2: <FabProvider at 82248d0: UMich> 000000000001a608: <Comment at 8231b88: Hexon at 2001-04-27 14:34:31.51> 0000000000060994: <Review at 8230a28> 0000000000060b00: <Comment at 822ff60: Hexon at 2001-04-27 14:34:31.51> 000000000001e4b1: <User at 8224550: Hexon> 000000000001e4b0: <Review at 8232308> 000000000001e4b2: <FabProvider at 822ff00: UMich> 000000000001e4b2: <FabProvider at 822ff00: UMich> 000000000001e5e7: <Comment at 8233138: Hexon at 2001-04-25 15:58:12.17> 0000000000021943: <User at 81e3a38: Hexon> 0000000000021942: <Review at 8232ca0> ...
zodb_index
-style reference
indexing?We have over 4000 tests for our basic objects. The existence of this test suite gives us confidence when we refactor the code.
Consider this simple function:
def f(s, val): if val < 0: raise ValueError, 'val cannot be negative' elif val == 42: print 'The answer!' return s * val
from quixote.test.unittest import TestScenario, parse_args, run_scenarios import module tested_modules = ['module'] class MyFunctionTest (TestScenario): def setup(self): pass def shutdown(self): pass def check_func(self): "Test the function's output: 6" # Test the null case (val == 0) self.test_val( "module.f('', 0)", '') self.test_val( "module.f('abc', 0)", '') # Test the identity (val == 1) self.test_val( "module.f('', 1)", '') self.test_val( "module.f('abc', 1)", 'abc') # Test a real case (val == 3) self.test_val( "module.f('', 3)", '') self.test_val( "module.f('abc', 3)", 'abcabcabc') if __name__ == "__main__": (scenarios, options) = parse_args() run_scenarios (scenarios, options)
ute /tmp>python test.py FunctionTest: ok: Test the function's output ('func') (6 tests passed) passed) ok: 9 tests passed ute /tmp>
Or, in case of failure:
ute /tmp>python test.py MyFunctionTest: not ok: Test the function's output ('func') (6 tests expected, 6 run, 2 failed) not ok: module.f('', 0) != '' (raised ValueError: "val cannot be negative") failed at test.py, line 12 (in 'check_func()'): self.test_val( "module.f('', 0)", '') File "/home/amk/src/mems/quixote/test/unittest.py", line 337, in test_val val = eval (code, globals, locals) File "<string>", line 0, in ? File "module.py", line 3, in f raise ValueError, 'val cannot be negative' not ok: module.f('abc', 0) != '' (raised ValueError: "val cannot be negative") ... not ok: 6 tests expected, 6 run, 2 failed ute /tmp>
ute /tmp>python test.py -c MyFunctionTest: ok: Test the function's output ('func') (6 tests passed) passed) ok: 6 tests passed code coverage: module: 83.3% (5/6) ute /tmp>python test.py -c -v ... additional output while running the tests deleted ... ok: 6 tests passed code coverage: module: . 10: def f(s, val): 9: if val < 0: 1: raise ValueError, 'val cannot be negative' 8: elif val == 42: >>>>>> print 'The answer!' . 8: return s * val 83.3% (5/6) kronos /tmp>
ute proto3>~/src/mems/tools/run_tests.py -r . looking for test scripts...found 45 ok: lib/test/test_pvalue.py: 118 tests passed ok: lib/test/test_range.py: 129 tests passed ok: lib/test/test_unit.py: 109 tests passed ... ok: template/test/test_eqtemplate.py: 14 tests passed ok: prc/test/test_inter.py: 0 tests passed ok: 4000 tests passed
Code coverage: grouch.valuetype: 36.6% (168/459) mems.run.process_run: 43.1% (202/469) mems.tools.unittest: 45.4% (237/522) mems.lib.base: 46.9% (75/160) grouch.util: 48.1% (26/54) mems.process.process_module: 52.4% (100/191) ...
Quixote is our environment for building Web applications.
Design goals:
Features:
An app is a Python package whose name is specified in a
config. file. (mems.ui
,
quixote.demo
)
webapp/ __init__.py module1.py module2.py pages1.ptl pages2.ptl
A URL is mapped to a callable Python object by traversing objects starting from the base package.
http://www/ | calls | webapp._q_index |
http://www/simple | calls | webapp.simple |
http://www/run/ | calls | webapp.run._q_index |
__init__.py:
_q_exports = ["simple", "error", "widgets"] import sys from quixote.demo.pages import _q_index from quixote.demo.widgets import widgets from quixote.demo.integer_ui import IntegerUI def simple (request): # This function returns a plain text document, not HTML. request.response.set_content_type("text/plain") return "This is the Python function 'quixote.demo.simple'.\n"
request
wraps up:
Most templating syntaxes look like HTML with a bit of additional syntax.
PHP |
<? for ($i=1; $i<10; $i++) print $i;> |
ASP |
<% addr = Request.form("email"); %> |
DTML | <dtml-var "row"> or <!--#var "row"--> |
Our syntax looks completely different, though still familiar...
pages.ptl:
template _q_index(request): print "debug message from the index page" """ <html> <head><title>Quixote Demo</title></head> <body> <h1>Hello, world!</h1> """ "<table>" for i in range(10): make_row(i) "</table>" template make_row(num): "<tr><td colspan=%i>%i</td></tr>" % (num, num)
When Python evaluates a lone expression, it discards the result:
def f(): string.lower('abc') return 1
In PTL, the result is converted to a string and appended to the output.
template numbers(n): for i in range(n): i " " # add whitespace
An import hook lets us import PTL files as if they're regular Python modules:
from quixote import enable_ptl enable_ptl() from webapp.pages import simple
vi
and Emacs modes for
it._q_index (request)
: string
When traversal stops at a module or package, this is the default
function name that's tried.
_q_access (request)
When found along the way, this function is called and must not
raise an exception to let traversal continue.
_q_getname (request, component)
: object
When a name isn't found, _q_getname
will be called.
If it returns an object, traversal will continue with this
object.
Access control function; traversal can go no further if it raises an exception.
def _q_access (request): from mems.ui.lib.errors import NotLoggedInError if request.session.user is None: raise NotLoggedInError, ("You must be signed in to view runcards.")
This saves us from having to write checking code for every public function. Instead, we can just put an access restriction on the whole module.
"/run/200/" is a more readable URL than "/run/?run_id=200".
_q_getname
gets called with the current request
and the component of the URL path. If it returns an object,
traversal continues with that object.
mems/ui/run/__init__.py
def _q_getname (request, component): return RunUI(request, component) class RunUI: _q_exports = ['details', 'check', ...] def __init__ (self, request, component): run_db = get_run_database() self.run = run_db.get_run(int(component)) template _q_index (self, request): # /run/200/ ... return index page ... template details (self, request): # /run/200/details/ ... return a more detailed page ...
An optional part of Quixote that lives in the
quixote.form
subpackage, the framework lets us
quickly implement basic (and not so basic) HTML forms.
Features:
Is a container for child widgets, and has three methods:
Widgets then produce HTML, and handle parsing of the resulting form element.
class LoginForm (Form): def __init__ (self, request): Form.__init__(self) self.add_widget( "string", "user_id", value=self.default_user_id, title="User ID", size=15, maxlength=32) self.add_widget( "password", "password", title="Password", size=15, maxlength=32) self.add_submit_button(name='login', value="Sign In") template render (self, request, action_url): title = "Sign In to the MEMS Exchange" standard.header(request, title, tree_info=[]) for widget in self.widget_order: widget.render(request) "<br>" for button in self.submit_buttons: button.render(request) standard.footer(title)
def process (self, request): form_data = Form.process(self, request) user_id = form_data["user_id"] password = form_data["password"] if user ID check fails: self.error['user_id'] = "Invalid user ID" elif not valid_password(user, password): self.error['password'] = "Invalid password" self.user = user return form_data def action (self, request, submit, form_data): request.session.set_user(self.user) self.user.record_login(request.get_environ("REMOTE_ADDR"))
These slides:
http://www.amk.ca/talks/
More on the MX toolset:
http://www.amk.ca/python/writing/mx-architecture/
ZODB/ZEO package:
http://www.amk.ca/zodb/
Grouch:
http://www.mems-exchange.org/software/grouch/
Quixote:
http://www.mems-exchange.org/software/quixote/
Join the quixote-users mailing list.