Conversation
This is a start towards eliminating the large import blocks that pull things from all over LabThings.
|
We should use
|
|
It's probably not helpful to expend too much energy on re-exports vs My code currently says |
Barecheck - Code coverage reportTotal: 88.55%Your code coverage diff: 2.80% ▴ Uncovered files and lines |
|
This now includes all the symbols @julianstirling recommended, except |
|
I also feel a little bit like the top level namespace could get quite disorganised (e.g. once |
I would consider exporting import labthings as lt
lt.blob.PNGBlob
lt.blob.JPEGBlobWe probably want to do a bit of routing around the microscope codebase. The examples and the tests to see what else we tend to use? It would be good to make the unit tests use top level import where possible so that they check that the outward facing API is stable. |
By using the top level imports in our tests, we make it more likely we'll catch any issues if something changes.
That feels like a good solution. It might then be sensible to add an
Was working on that as you wrote the comment :) |
|
Similar to |
|
I think I agree with that.
|
|
I've finally found the issue I had trying to do this before: if I pull things up from submodules of That isn't a problem for |
I don't understand the relationship between |
|
I'd really like to organise the dependencies - but I am a bit stumped as to the right thing to do. I think it would be clear to group them, so they are Also, because dependency type hints appear in lots of function arguments, there probably is an argument for keeping them short - but it's also helpful to know they are dependencies. Should we consider a naming convention, like ending them all in An option worth considering would be adding a submodule |
I've moved dependencies into a convenience module `deps`, so now dependencies, outputs, and blob subclasses are imported as `lt.blob.MyBlob` etc. to avoid cluttering the global namespace too much. `deps` is used rather than `dependencies` to avoid circular imports and allow for future rearrangements, as well as keeping type annotations shorter if it's imported as `lt.deps.Whatever`. Test code is updated to use the new imports.
|
An alternative option for dependencies would be to move the implementation of import labthings_fastapi as lt
from labthings_fastapi.dependencies import WhateverDepsINeedThis would keep type hints short but hopefully also make it clear that they are dependencies. I think the |
|
Sorry, I hadn't seen your graphs before I made the last commits. That does look scary! I think this PR creates a reasonable API for people writing Things, and paves the way for rearrangements to happen under the hood. As such, it might be worth merging it soon and releasing I don't think your proposed structure will work quite like that, but I'm sure something along those lines will be possible, with a bit of thought. Splitting some of the client code into its own module will help there. I've not yet addressed the issue of broken client-only installs (#120). I'll suggest a temporary fix for that and we can figure out how to split the client code for the next release. |
|
I've been doing a bit of reading about dependencies, it is something that I don't fully understand but I have had far fewer issues. Looking at advice there seems to be bits around the place including:
Personally I try to avoid putting code in One interesting thing to look at is
As for reviewing this. I would like to play with how it works on the microscope repo before we merge. |
|
I'm not totally sure I follow what you mean with relative imports "except with single I have already tried out the current imports on the test suite, and it certainly tidies things up. Clearly we'll need to reorganise the microscope imports before or after this goes in, so it makes sense to test it out to avoid releasing an API that gets binned again; if we like it, it's not wasted work. Reorganising the code is a bigger job, for a future PR. I think, though, it is worth having an API we can keep stable while we refactor, even if it only lasts a few versions - hence this PR. I see the value of the Pandas solution, with "summary" imports that aren't in I'll close by making another suggestion: rather than putting these symbols in the top level, we could do what |
As in within the same sub-package is normally fine: from .foo import barBut using it to access a different sub-package isn't recommended: from ..foo import bar
Oh, I think this could be nice! Because hopefully it creates a bit of a separation between those writing a server, who need Things, Thing Severs, and property decorators, and those just using LabThings to talk to an existing server: from labthings_fastapi import server_api as ltOR from labthings_fastapi import client_api as lt |
julianstirling
left a comment
There was a problem hiding this comment.
Two suggestion with which !296 on the microscope works in simulation.
|
I'm just looking at what is needed to merge this. The examples, and the docs need updating as well as the test. As for the examples. There are some quite old camera examples which are probably going out of sync with the codebase. It probably feels better to point to the v3 development for more complex examples than to have more complex examples that don't work? |
yes, I think I mentioned that in a previous MR as well. I can delete the camera examples now they are superseded by the openflexure server. No point duplicating effort there. Those examples can always come back if we want them later. |
Co-authored-by: Julian Stirling <julian@julianstirling.co.uk>
|
Wanted to add my thoughts about the dependency structure conversation. Python dependencies are a complicated nightmare at the best of times!
Absolutely agree with this point! This can get messy, and is quite difficult to debug. Sub-packages should not have relative ( The dependency map tells me that we have way too many packages. I think the hierarchical approach could be a good one to start with. I also like the idea of splitting out the server and client APIs into separate packages as they serve very distinct and unique purposes. Much like the pandas solution, I'd also advise keeping the init.py files as empty as possible. As said here:
In terms of exactly how we restructure the code, I think a useful exercise to do would be to identify which functions/imports are the main culprits for circular imports, and move them into their own sub-package/packages, like Richard said here:
We need to try and keep each sub-package as decoupled from others as possible, so grouping them by function/use case (as per Julian's diagram) could be a good way to do this. |
|
Thanks @bprobert97 and @julianstirling, that all makes a lot of sense. Thinking through how it works, I think mostly it can be structured so sub packages don't have too many interdependencies. I think typing makes it harder, and there are certainly likely to be types that require I have not been particularly diligent about using the |
|
I think it would be good to decide how we want to proceed with this PR, it's getting quite a long discussion already. I think the sensible options are:
I think I might prefer (2), but I don't have a strong opinion. I'm also not trying to exclude other options, I just haven't thought of any... |
|
Also: clearly the refactoring exercise will conflict horribly with everything else, so we'll want to make sure there are not too many other open PRs when it happens... |
I'd go for 1. It certainly improves things, and then we can then carefully reorganise the code base behind so that the the same imports work. I do think the docs and examples need updating before it goes in. |
|
I think 1 is the simplest answer for now - it makes improvements that we can build on. More documentation would also be great! Do we need to open an issue for the rest of the refactoring changes? |
Imports of `labthings_fastapi` within the package are now consistently changed to relative (.) imports. Tests now use `import labthings_fastapi as lt` wherever possible. Documentation has been updated to use the new import style.
|
I think this is ready for re-review @julianstirling @bprobert97. |
|
Looks good to me! |
julianstirling
left a comment
There was a problem hiding this comment.
Awesome. Thank you Richard.


Currently, there are lots of imports from quite deep within this library that should be easier to find. @julianstirling suggested adding a useful set of top-level objects to the top level
__init__module in #99 , so thatcould become
It might even become a helpful convention to
import labthings as ltand then uselt.Thingetc.This feels like it should be simple, and all the symbols above work just fine. However, I have run into problems in the past when I tried to do this (specifically for
labthings_fastapi.dependencies) because of circular references. This might limit how many things can usefully be exposed at top level.Also, at the moment there is one
labthings_fastapimodule that is both client and server. Iflabthings_fastapiis installed without theserverextra, i.e. it's being used only as a client over HTTP, adding these top level imports will break it - becauselabthings_fastapi.thinghas a hard dependency onfastapi, for example. I suspect the resolution to that is to split the package into a client and server module - but I'd welcome opinions on what makes most sense.Depends on #110 (now merged)
Closes #99