-
Notifications
You must be signed in to change notification settings - Fork 180
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
feat(protocol-designer): load module in python file #17483
Conversation
@@ -70,6 +70,12 @@ describe('createFile selector', () => { | |||
afterEach(() => { | |||
vi.restoreAllMocks() | |||
}) | |||
const entities = { | |||
moduleEntities: v7Fixture.moduleEntities, |
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.
turns out the v7Fixture doesn't have a module - should i add one to the fixture so we can test that it works in the unit test?
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.
If you want to! But like the comment below says, the point of the tests in this file is just to make sure createFile()
/createPythonFile()
spit out something that looks like a protocol. We'll have other unit tests in other files to make sure that the generated Python code is actually correct for all possible types of modules/labware/etc.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## edge #17483 +/- ##
=======================================
Coverage 26.16% 26.17%
=======================================
Files 3191 3191
Lines 230143 230174 +31
Branches 9808 9815 +7
=======================================
+ Hits 60220 60251 +31
Misses 169898 169898
Partials 25 25
Flags with carried forward coverage won't be shown. Click here to find out more.
|
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.
Hey! So my plan for testing was that in pythonFile.test.ts
, we would have fine-grained tests for each of the Python generator functions.
For for this PR, we would add tests for getLoadModules()
to pythonFile.test.ts
, which would show that the Python code looks like for loading modules.
@@ -70,6 +70,12 @@ describe('createFile selector', () => { | |||
afterEach(() => { | |||
vi.restoreAllMocks() | |||
}) | |||
const entities = { | |||
moduleEntities: v7Fixture.moduleEntities, |
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.
If you want to! But like the comment below says, the point of the tests in this file is just to make sure createFile()
/createPythonFile()
spit out something that looks like a protocol. We'll have other unit tests in other files to make sure that the generated Python code is actually correct for all possible types of modules/labware/etc.
@@ -51,9 +56,32 @@ export function pythonRequirements(robotType: RobotType): string { | |||
return `requirements = ${formatPyDict(requirements)}` | |||
} | |||
|
|||
export function pythonDefRun(): string { | |||
function getLoadModules( |
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.
Question: Do you prefer your files to go from high-level to low-level functions (so pythonDefRun()
comes before getLoadModules()
), or low-level functions to high-level functions?
I just want to pick a consistent order so that the file makes sense if someone reads it from top to bottom.
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.
i usually automatically do low-level functions to high-level functions, but i don't think we have a pattern. i can also make a new file for the load
functions to live in too.
function getLoadModules( | ||
moduleEntities: ModuleEntities, | ||
moduleRobotState: TimelineFrame['modules'] | ||
): string[] { |
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.
Hm, I was hoping that each of these sections like getLoadModules()
would return a string instead of an array, decorated with a comment like `# Load modules." to make the Python file easier to read.
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.
oh ya, i can do that! i wasn't sure.
(acc, moduleEntity) => [ | ||
...acc, | ||
`${moduleEntity.pythonName} = ${PROTOCOL_CONTEXT_NAME}.load_module("${ | ||
moduleEntity.model |
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.
I need to confirm that moduleEntity.model
is the correct Python identifier (https://docs.opentrons.com/v2/modules/setup.html#available-modules). Do you know if it is?
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 of them are, some of them are not. i'll fix it
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.
the newer modules's python identifier is the module model. so maybe i can map out the conversion for older modules and then assume that all of the unreleased future modules will follow the same module model pattern?
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.
Heh, so we can check with the PAPI team, but I think when I tried it, the API actually accepts a wider range of names than what's in the doc. So moduleEntity.model
might actually work -- I'd just need to trace through the Python code to confirm.
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.
oh that is nice to know! i made a fn to map out the old ones but can easily remove it
@@ -1,6 +1,5 @@ | |||
import type { OutputSelector, Selector } from 'reselect' | |||
declare module 'reselect' { | |||
// declaring type for createSelector with 15 selectors because the reselect types only support up to 12 selectors |
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.
lol, I had to hack up this file too.
But can you check to see if you need to touch this file at all? My understanding is that for 12 selectors or fewer, the base library already supports that, and you don't need to provide this override.
const pythonIdentifier = getModulePythonIdentifier(module.model) | ||
return `${ | ||
module.pythonName | ||
} = ${PROTOCOL_CONTEXT_NAME}.load_module("${pythonIdentifier}", "${ |
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.
Hey, don't "quote"
strings yourself -- could you use formatPyStr()
instead? It'll work correctly just in case pythonIdentifier
contains quotes or backslashes or other funny characters.
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.
oops, ya will update it!
expect(pythonDefRun(mockInvariantContext, mockInitialRobotState)).toBe( | ||
` | ||
def run(protocol: protocol_api.ProtocolContext): | ||
|
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.
Nit: Could you get rid of the blank line at here before the code in the function?
@@ -237,3 +238,25 @@ export const getLabwareLoadInfo = ( | |||
{} | |||
) | |||
} | |||
|
|||
export const getModulePythonIdentifier = (model: ModuleModel): string => { |
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.
Let's check if this function is really necessary.
But if it is, could you implement it with a {key: value}
map instead of switch/case
, just to make it more concise?
it('should generate the commands section', () => { | ||
expect(pythonDefRun(mockInvariantContext, mockInitialRobotState)).toBe( | ||
` | ||
def run(protocol: protocol_api.ProtocolContext): |
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.
Hm, the tests here could become a bit repetitive if we include the def run()
in all of them. It might be better to write more targeted unit tests and just test getLoadModules()
directly. (We already know that the def run()
line gets generated from the higher-level tests.)
): string { | ||
const pythonModules = Object.values(moduleEntities) | ||
.map(module => { | ||
const pythonIdentifier = getModulePythonIdentifier(module.model) |
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.
Hey, take a look here: https://github.com/Opentrons/opentrons/blob/edge/api/src/opentrons/protocol_api/validation.py#L373
It looks like any of the names in _MODULE_ALIASES
or _MODULE_MODELS
are acceptable as arguments to protocol.load_module()
. Are all of our model names on one of those lists?
Also, could you add a comment with a link to that file so that we remember where we got the names from?
return `${ | ||
module.pythonName | ||
} = ${PROTOCOL_CONTEXT_NAME}.load_module(${formatPyStr( | ||
pythonIdentifier |
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.
I think just using module.model
directly is fine now if we don't need the lookup function.
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.
yup! fair.
Tried it on your branch. Looks nice!
|
addresses a bit of AUTH-1092
Overview
First, I added to the
createPythonFile
so modules get added. Then I cleaned upcreateFile
for JSON so we usegetInvariantContext
instead of 4 separate selectors for the entities.Test Plan and Hands on Testing
Smoke test a protocol by exporting python. Should work as expected
Changelog
createFile()
getLoadModules
to return an array of each load module for pythonReview requests
I removed what we changed in the reselect.d.ts - i think that's ok since we now only need the 12 selectors in
createFile
Risk assessment
low, behind ff