Skip to content

Commit

Permalink
Fix #24 -- Move config to pyproject.yaml
Browse files Browse the repository at this point in the history
The python community has widely adopeted PIP 621. Therefore,
it seems the best place to implement it in this package too.

There are two key changes:

1. The config moves to `tool.importmap`.
2. The importmap config allows adding multiple entries.

Additionally, this commits removes two dependencies:
* marshmellow
* tomli (python_version>=12.0)
  • Loading branch information
codingjoe committed Nov 24, 2023
1 parent 7b91e90 commit f525f60
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 883 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,23 @@ INSTALLED_APPS = [
]
```

### 2. Create an `importmap.toml` file
### 2. Configuring an import map

This should live next to your `manage.py` file.
Here you'll add a list of "packages" you want to use.
You JavaScript dependencies are conveniently located in your`pyproject.toml` file.

The "name" can be anything, but should probably be the same as what it you would import from in typical bundling setups (i.e. `import React from "react"`).

The "source" will get passed on to the [jspm.org generator](https://jspm.org/docs/api#install), but is basically the `<npm package>@<version>` you want to use.
They are listed under `[tool.importmap.dependencies]` and you can add them there. The format is `name = "version"`,
similar to how you would add a dependency to your `package.json` file.

```toml
[[packages]]
name = "react"
source = "[email protected]"
# pyproject.toml
[tool.importmap.dependencies]
react = "17.0.2"
react-dom = "17.0.2"
```

[jspm.org generator](https://jspm.org/docs/api#install) is used lock and serve the dependencies,
but is basically just like installing them via `npm i <npm package>@<version>`.

### 3. Run `importmap_generate`

To resolve the import map, you'll need to run `python manage.py importmap_generate`.
Expand Down
59 changes: 17 additions & 42 deletions importmap/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,22 @@
import json
import logging
import os
from pathlib import Path

import tomli
from marshmallow import Schema, fields
try:
import tomllib
except ImportError:
import tomli as tomllib

from .generator import ImportmapGenerator

logger = logging.getLogger(__name__)


DEFAULT_CONFIG_FILENAME = "importmap.toml"
DEFAULT_CONFIG_FILENAME = "pyproject.toml"
DEFAULT_LOCK_FILENAME = "importmap.lock"


class PackageSchema(Schema):
name = fields.String(required=True)
source = fields.String(required=True)
# preload
# vendor, or vendor all is one option?


class ConfigSchema(Schema):
packages = fields.List(fields.Nested(PackageSchema), required=True)


class LockfileSchema(Schema):
config_hash = fields.String(required=True)
importmap = fields.Dict(required=True)
importmap_dev = fields.Dict(required=True)


def hash_for_data(data):
return hashlib.md5(json.dumps(data, sort_keys=True).encode("utf-8")).hexdigest()

Expand All @@ -42,8 +28,8 @@ def __init__(
config_filename=DEFAULT_CONFIG_FILENAME,
lock_filename=DEFAULT_LOCK_FILENAME,
):
self.config_filename = config_filename
self.lock_filename = lock_filename
self.config_file = Path(config_filename)
self.lock_file = Path(lock_filename)
self.load()

@classmethod
Expand All @@ -70,7 +56,7 @@ def load(self):
# No config = no map and no lockfile
self.map = {}
self.map_dev = {}
self.delete_lockfile()
self.lock_file.unlink(missing_ok=True)
return

lockfile = self.load_lockfile()
Expand All @@ -95,34 +81,23 @@ def generate(self, force=False):
self.save_lockfile(lockfile)

def load_config(self):
# TODO raise custom exceptions

if not os.path.exists(self.config_filename):
logger.warning(f"{self.config_filename} not found")
if not self.config_file.exists():
return {}
with self.config_file.open("rb") as f:
pyproject = tomllib.load(f)

with open(self.config_filename, "r") as f:
# why doesn't tomli.load(f) work?
toml_data = tomli.loads(f.read())

return ConfigSchema().load(toml_data)
return pyproject["tool"]["importmap"]

def load_lockfile(self):
if not os.path.exists(self.lock_filename):
if not self.lock_file.exists():
return {}

with open(self.lock_filename, "r") as f:
json_data = json.load(f)

return LockfileSchema().load(json_data)
with self.lock_file.open("r") as f:
return json.load(f)

def save_lockfile(self, lockfile):
with open(self.lock_filename, "w+") as f:
with self.lock_file.open("w+") as f:
json.dump(lockfile, f, indent=2, sort_keys=True)

def delete_lockfile(self):
if os.path.exists(self.lock_filename):
os.remove(self.lock_filename)

def generate_map(self, *args, **kwargs):
return ImportmapGenerator.from_config(self.config, *args, **kwargs).generate()
11 changes: 5 additions & 6 deletions importmap/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ def __init__(self, targets, development=False, provider="jspm"):

@classmethod
def from_config(cls, config, *args, **kwargs):
targets = []

for map in config["packages"]:
targets.append(map["source"])

return cls(targets, *args, **kwargs)
return cls(
[f"{package}@{version}" for package, version in config["dependencies"].items()],
*args,
**kwargs,
)

def get_env(self):
if self.development:
Expand Down
Loading

0 comments on commit f525f60

Please sign in to comment.