-
Notifications
You must be signed in to change notification settings - Fork 147
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
[WIP] Refactor the pipfile API into a functional one #57
Conversation
in order to get this workable, let's get the searching working again. |
Yea, Weds and Thurs are my OSS days, so I'm working on this today. |
looking forward to it :) |
@dstufft any update on this? should i try to take it over from here? |
ping |
@dstufft Ping? |
P.S. I'm not against our current API — I think it's quite a sane approach, but after looking at the Pip codebase, I understand that it takes immutability into consideration quite often. A part of me thinks that this is overkill for this project, as these are quite simple functions. |
But I defer to you @dstufft :) |
P.S. I think it would possibly also reduce the maintainability of this library. |
@kennethreitz @dstufft: is this still in progress, or did you all decide to keep the API as-is? I'm looking forward to pyupio/safety#47, but it looks like it's blocked on this for now. |
Agreed, it would be really nice for people looking to start supporting Pipfile if the API is stable |
This is a WIP, it does not currently actually do all of the things that it would need to do and some of the things it currently does are just flat out wrong. However, it's close enough that it gives a reasonable idea (I think) of what the API and usage would be.
The core idea here is that we have two objects,
Pipfile
andPipfileLock
and these two objects are completely immutable. This makes it a ton easier to reason about in code (because nothing can ever change the object out from underneath you) and I think it's a good direction for these packaging libraries to head towards in general.You don't directly create these objects in the public API, instead you're given a few functions that will parse either a
Pipfile
or aPipfile.lock
and give you back already created instances of them. In the case that you want to modify them you simply use the pyristant transformers API or the evolvers API and you can safely do that without having to worry about who else might hold a reference to that object. In addition,Pipfile
objects have alock()
method which allow you to create aPipfileLock
object given an existingPipfile
and a resolved set of sources and packages.Basically, an example of what this API would look like in use is:
While this PR currently does handle reading and saving files (and this works well enough for the lock files) it needs to get a lot better at how it handles the saving out a TOML file. In particular, it currently just completely overwrites the existing TOML file which blows away any comments and possibly changes the formatting of the file itself. This is largely a shortcoming of the TOML library that we're using, but ideally we'd be able to pass in an existing file to
dumps()
in the case of attempting to dump aPipfile
and it would instead merge our changes into that file instead of overwriting it. This merging could happen automatically in the case ofpipfile.save()
.Here are a few things to think about in terms of this PR:
dumps()
,dump()
,loads()
, andload()
that handle bothPipfile
andPipfile.lock
. In the case of the load functions it asks for thePipfile
and optionally you can pass in aPipfile.lock
if they exist, but the dump functions it only accepts one at a time, either aPipfile
or aPipfileLock
. Should we instead have separate functions for loading/dumping aPipfile
and aPipfileLock
?pipfile.save()
function will (eventually) re-do the recursive search up forPipfile
andPipfile.lock
, and failing to find one it will just save them in the root directory (defaults to.
). This does cause a race condition where someone could place aPipfile
in the directory tree between the root directory and where we originally loaded the files from. If this is a case that we care about, then perhaps the ideal thing to do is include the file path that we actually loaded the files from in thepipfile.find()
API.Pipfile
andPipfileLock
objects are intentionally completely divorced from any sort of filesystem or even filesystem representation so we can't just attach the filenames/locations we loaded them from to those objects themselves, we could return it as another value in the tuple returned bypipfile.find()
, however we need to either return a single value which is the directory we loaded them from (and we possibly get the same race condition if someone writes aPipfile
after we read from apipfile
... though I don't think this is a case we need to care about) or we need to return 2 additional values that point to the specific files which starts to get a lot more unwieldy. Personally I would just change thepipfile.find()
API to return(directory, Pipfile, PipfileLock or None)
.Pipfile.lock
file, leaving that up to the calling project. I think that this is the right thing to do, because different projects may want to do different things with it (for example, some may want to just install from the lockfile anyways, or some might want to ignore it and regenerate the lockfile). I could also see a use case for the same project to do different things depending on it's configuration.Pipfile
andPipfileLock
are immutable, modifying them requires using the pyrisistent transformers API. This is a powerful API that allows a lot of flexibility in modifying an immutable object, you can see below for an example of modifying aPipfile
object. This is a bit more work than just using mutable objects, but there are nice benefits to using immutable objects, and generally the primary unit of work is going to be taking an existingPipfile
, resolving it's dependencies, and then writing it out to aPipfile.lock
(aka, whatPipfile.lock()
does). If there are other things that are common we can also add additional shortcuts to them as well.That is a whole lot of words. I want to see what others think before I push too much further down this path, but overall I think this creates a pretty decent setup that gives nice, immutable objects while also providing good convenience APIs.