Skip to content

project

project

Project facade to orchestrate models, experiments, and analysis.

Project

Bases: GuardedBase

Central API for managing a diffraction data analysis project.

Provides access to structures, experiments, analysis, and summary.

Source code in src/easydiffraction/project/project.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
class Project(GuardedBase):
    """
    Central API for managing a diffraction data analysis project.

    Provides access to structures, experiments, analysis, and summary.
    """

    # ------------------------------------------------------------------
    # Initialization
    # ------------------------------------------------------------------
    def __init__(
        self,
        name: str = 'untitled_project',
        title: str = 'Untitled Project',
        description: str = '',
    ) -> None:
        super().__init__()

        self._info: ProjectInfo = ProjectInfo(name, title, description)
        self._structures = Structures()
        self._experiments = Experiments()
        self._tabler = TableRenderer.get()
        self._plotter = Plotter()
        self._analysis = Analysis(self)
        self._summary = Summary(self)
        self._saved = False
        self._varname = varname()
        self._verbosity: VerbosityEnum = VerbosityEnum.FULL

    # ------------------------------------------------------------------
    # Dunder methods
    # ------------------------------------------------------------------
    def __str__(self) -> str:
        """Human-readable representation."""
        class_name = self.__class__.__name__
        project_name = self.name
        structures_count = len(self.structures)
        experiments_count = len(self.experiments)
        return (
            f"{class_name} '{project_name}' "
            f'({structures_count} structures, '
            f'{experiments_count} experiments)'
        )

    # ------------------------------------------------------------------
    # Public read-only properties
    # ------------------------------------------------------------------

    @property
    def info(self) -> ProjectInfo:
        """Project metadata container."""
        return self._info

    @property
    def name(self) -> str:
        """Convenience property for the project name."""
        return self._info.name

    @property
    def full_name(self) -> str:
        """
        Return the full project name (alias for :attr:`name`).

        Returns
        -------
        str
            The project name.
        """
        return self.name

    @property
    def structures(self) -> Structures:
        """Collection of structures in the project."""
        return self._structures

    @structures.setter
    @typechecked
    def structures(self, structures: Structures) -> None:
        self._structures = structures

    @property
    def experiments(self) -> Experiments:
        """Collection of experiments in the project."""
        return self._experiments

    @experiments.setter
    @typechecked
    def experiments(self, experiments: Experiments) -> None:
        self._experiments = experiments

    @property
    def plotter(self) -> Plotter:
        """Plotting facade bound to the project."""
        return self._plotter

    @property
    def tabler(self) -> TableRenderer:
        """Tables rendering facade bound to the project."""
        return self._tabler

    @property
    def analysis(self) -> Analysis:
        """Analysis entry-point bound to the project."""
        return self._analysis

    @property
    def summary(self) -> Summary:
        """Summary report builder bound to the project."""
        return self._summary

    @property
    def parameters(self) -> list:
        """Return parameters from all structures and experiments."""
        return self.structures.parameters + self.experiments.parameters

    @property
    def as_cif(self) -> str:
        """Export whole project as CIF text."""
        # Concatenate sections using centralized CIF serializers
        return project_to_cif(self)

    @property
    def verbosity(self) -> str:
        """
        Project-wide console output verbosity.

        Returns
        -------
        str
            One of ``'full'``, ``'short'``, or ``'silent'``.
        """
        return self._verbosity.value

    @verbosity.setter
    def verbosity(self, value: str) -> None:
        """
        Set project-wide console output verbosity.

        Parameters
        ----------
        value : str
            ``'full'`` for multi-line output, ``'short'`` for one-line
            status messages, or ``'silent'`` for no output.
        """
        self._verbosity = VerbosityEnum(value)

    # ------------------------------------------
    #  Project File I/O
    # ------------------------------------------

    def load(self, dir_path: str) -> None:
        """
        Load a project from a given directory.

        Loads project info, structures, experiments, etc.
        """
        # TODO: load project components from files inside dir_path
        raise NotImplementedError('Project.load() is not implemented yet.')

    def save(self) -> None:
        """Save the project into the existing project directory."""
        if self._info.path is None:
            log.error('Project path not specified. Use save_as() to define the path first.')
            return

        console.paragraph(f"Saving project 📦 '{self.name}' to")
        console.print(self.info.path.resolve())

        # Ensure project directory exists
        self._info.path.mkdir(parents=True, exist_ok=True)

        # Save project info
        with (self._info.path / 'project.cif').open('w') as f:
            f.write(self._info.as_cif())
            console.print('├── 📄 project.cif')

        # Save structures
        sm_dir = self._info.path / 'structures'
        sm_dir.mkdir(parents=True, exist_ok=True)
        console.print('├── 📁 structures/')
        for structure in self.structures.values():
            file_name: str = f'{structure.name}.cif'
            file_path = sm_dir / file_name
            with file_path.open('w') as f:
                f.write(structure.as_cif)
                console.print(f'│   └── 📄 {file_name}')

        # Save experiments
        expt_dir = self._info.path / 'experiments'
        expt_dir.mkdir(parents=True, exist_ok=True)
        console.print('├── 📁 experiments/')
        for experiment in self.experiments.values():
            file_name: str = f'{experiment.name}.cif'
            file_path = expt_dir / file_name
            with file_path.open('w') as f:
                f.write(experiment.as_cif)
                console.print(f'│   └── 📄 {file_name}')

        # Save analysis
        with (self._info.path / 'analysis.cif').open('w') as f:
            f.write(self.analysis.as_cif())
            console.print('├── 📄 analysis.cif')

        # Save summary
        with (self._info.path / 'summary.cif').open('w') as f:
            f.write(self.summary.as_cif())
            console.print('└── 📄 summary.cif')

        self._info.update_last_modified()
        self._saved = True

    def save_as(
        self,
        dir_path: str,
        temporary: bool = False,
    ) -> None:
        """Save the project into a new directory."""
        if temporary:
            tmp: str = tempfile.gettempdir()
            dir_path = pathlib.Path(tmp) / dir_path
        self._info.path = dir_path
        self.save()

    # ------------------------------------------
    # Plotting
    # ------------------------------------------

    def _update_categories(self, expt_name: str) -> None:
        for structure in self.structures:
            structure._update_categories()
        self.analysis._update_categories()
        experiment = self.experiments[expt_name]
        experiment._update_categories()

    def plot_meas(
        self,
        expt_name: str,
        x_min: float | None = None,
        x_max: float | None = None,
        x: object | None = None,
    ) -> None:
        """
        Plot measured diffraction data for an experiment.

        Parameters
        ----------
        expt_name : str
            Name of the experiment to plot.
        x_min : float | None, default=None
            Lower bound for the x-axis range.
        x_max : float | None, default=None
            Upper bound for the x-axis range.
        x : object | None, default=None
            Optional explicit x-axis data to override stored values.
        """
        self._update_categories(expt_name)
        experiment = self.experiments[expt_name]

        self.plotter.plot_meas(
            experiment.data,
            expt_name,
            experiment.type,
            x_min=x_min,
            x_max=x_max,
            x=x,
        )

    def plot_calc(
        self,
        expt_name: str,
        x_min: float | None = None,
        x_max: float | None = None,
        x: object | None = None,
    ) -> None:
        """
        Plot calculated diffraction pattern for an experiment.

        Parameters
        ----------
        expt_name : str
            Name of the experiment to plot.
        x_min : float | None, default=None
            Lower bound for the x-axis range.
        x_max : float | None, default=None
            Upper bound for the x-axis range.
        x : object | None, default=None
            Optional explicit x-axis data to override stored values.
        """
        self._update_categories(expt_name)
        experiment = self.experiments[expt_name]

        self.plotter.plot_calc(
            experiment.data,
            expt_name,
            experiment.type,
            x_min=x_min,
            x_max=x_max,
            x=x,
        )

    def plot_meas_vs_calc(
        self,
        expt_name: str,
        x_min: float | None = None,
        x_max: float | None = None,
        show_residual: bool = False,
        x: object | None = None,
    ) -> None:
        """
        Plot measured vs calculated data for an experiment.

        Parameters
        ----------
        expt_name : str
            Name of the experiment to plot.
        x_min : float | None, default=None
            Lower bound for the x-axis range.
        x_max : float | None, default=None
            Upper bound for the x-axis range.
        show_residual : bool, default=False
            When ``True``, include the residual (difference) curve.
        x : object | None, default=None
            Optional explicit x-axis data to override stored values.
        """
        self._update_categories(expt_name)
        experiment = self.experiments[expt_name]

        self.plotter.plot_meas_vs_calc(
            experiment.data,
            expt_name,
            experiment.type,
            x_min=x_min,
            x_max=x_max,
            show_residual=show_residual,
            x=x,
        )

    def plot_param_series(self, param: object, versus: object | None = None) -> None:
        """
        Plot a parameter's value across sequential fit results.

        Parameters
        ----------
        param : object
            Parameter descriptor whose ``unique_name`` identifies the
            values to plot.
        versus : object | None, default=None
            A diffrn descriptor (e.g.
            ``expt.diffrn.ambient_temperature``) whose value is used as
            the x-axis for each experiment.  When ``None``, the
            experiment sequence number is used instead.
        """
        unique_name = param.unique_name
        versus_name = versus.name if versus is not None else None
        self.plotter.plot_param_series(
            unique_name,
            versus_name,
            self.experiments,
            self.analysis._parameter_snapshots,
        )

__str__()

Human-readable representation.

Source code in src/easydiffraction/project/project.py
57
58
59
60
61
62
63
64
65
66
67
def __str__(self) -> str:
    """Human-readable representation."""
    class_name = self.__class__.__name__
    project_name = self.name
    structures_count = len(self.structures)
    experiments_count = len(self.experiments)
    return (
        f"{class_name} '{project_name}' "
        f'({structures_count} structures, '
        f'{experiments_count} experiments)'
    )

analysis property

Analysis entry-point bound to the project.

as_cif property

Export whole project as CIF text.

experiments property writable

Collection of experiments in the project.

full_name property

Return the full project name (alias for :attr:name).

Returns:

Type Description
str

The project name.

info property

Project metadata container.

load(dir_path)

Load a project from a given directory.

Loads project info, structures, experiments, etc.

Source code in src/easydiffraction/project/project.py
175
176
177
178
179
180
181
182
def load(self, dir_path: str) -> None:
    """
    Load a project from a given directory.

    Loads project info, structures, experiments, etc.
    """
    # TODO: load project components from files inside dir_path
    raise NotImplementedError('Project.load() is not implemented yet.')

name property

Convenience property for the project name.

parameters property

Return parameters from all structures and experiments.

plot_calc(expt_name, x_min=None, x_max=None, x=None)

Plot calculated diffraction pattern for an experiment.

Parameters:

Name Type Description Default
expt_name str

Name of the experiment to plot.

required
x_min float | None

Lower bound for the x-axis range.

None
x_max float | None

Upper bound for the x-axis range.

None
x object | None

Optional explicit x-axis data to override stored values.

None
Source code in src/easydiffraction/project/project.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def plot_calc(
    self,
    expt_name: str,
    x_min: float | None = None,
    x_max: float | None = None,
    x: object | None = None,
) -> None:
    """
    Plot calculated diffraction pattern for an experiment.

    Parameters
    ----------
    expt_name : str
        Name of the experiment to plot.
    x_min : float | None, default=None
        Lower bound for the x-axis range.
    x_max : float | None, default=None
        Upper bound for the x-axis range.
    x : object | None, default=None
        Optional explicit x-axis data to override stored values.
    """
    self._update_categories(expt_name)
    experiment = self.experiments[expt_name]

    self.plotter.plot_calc(
        experiment.data,
        expt_name,
        experiment.type,
        x_min=x_min,
        x_max=x_max,
        x=x,
    )

plot_meas(expt_name, x_min=None, x_max=None, x=None)

Plot measured diffraction data for an experiment.

Parameters:

Name Type Description Default
expt_name str

Name of the experiment to plot.

required
x_min float | None

Lower bound for the x-axis range.

None
x_max float | None

Upper bound for the x-axis range.

None
x object | None

Optional explicit x-axis data to override stored values.

None
Source code in src/easydiffraction/project/project.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def plot_meas(
    self,
    expt_name: str,
    x_min: float | None = None,
    x_max: float | None = None,
    x: object | None = None,
) -> None:
    """
    Plot measured diffraction data for an experiment.

    Parameters
    ----------
    expt_name : str
        Name of the experiment to plot.
    x_min : float | None, default=None
        Lower bound for the x-axis range.
    x_max : float | None, default=None
        Upper bound for the x-axis range.
    x : object | None, default=None
        Optional explicit x-axis data to override stored values.
    """
    self._update_categories(expt_name)
    experiment = self.experiments[expt_name]

    self.plotter.plot_meas(
        experiment.data,
        expt_name,
        experiment.type,
        x_min=x_min,
        x_max=x_max,
        x=x,
    )

plot_meas_vs_calc(expt_name, x_min=None, x_max=None, show_residual=False, x=None)

Plot measured vs calculated data for an experiment.

Parameters:

Name Type Description Default
expt_name str

Name of the experiment to plot.

required
x_min float | None

Lower bound for the x-axis range.

None
x_max float | None

Upper bound for the x-axis range.

None
show_residual bool

When True, include the residual (difference) curve.

False
x object | None

Optional explicit x-axis data to override stored values.

None
Source code in src/easydiffraction/project/project.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
def plot_meas_vs_calc(
    self,
    expt_name: str,
    x_min: float | None = None,
    x_max: float | None = None,
    show_residual: bool = False,
    x: object | None = None,
) -> None:
    """
    Plot measured vs calculated data for an experiment.

    Parameters
    ----------
    expt_name : str
        Name of the experiment to plot.
    x_min : float | None, default=None
        Lower bound for the x-axis range.
    x_max : float | None, default=None
        Upper bound for the x-axis range.
    show_residual : bool, default=False
        When ``True``, include the residual (difference) curve.
    x : object | None, default=None
        Optional explicit x-axis data to override stored values.
    """
    self._update_categories(expt_name)
    experiment = self.experiments[expt_name]

    self.plotter.plot_meas_vs_calc(
        experiment.data,
        expt_name,
        experiment.type,
        x_min=x_min,
        x_max=x_max,
        show_residual=show_residual,
        x=x,
    )

plot_param_series(param, versus=None)

Plot a parameter's value across sequential fit results.

Parameters:

Name Type Description Default
param object

Parameter descriptor whose unique_name identifies the values to plot.

required
versus object | None

A diffrn descriptor (e.g. expt.diffrn.ambient_temperature) whose value is used as the x-axis for each experiment. When None, the experiment sequence number is used instead.

None
Source code in src/easydiffraction/project/project.py
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
def plot_param_series(self, param: object, versus: object | None = None) -> None:
    """
    Plot a parameter's value across sequential fit results.

    Parameters
    ----------
    param : object
        Parameter descriptor whose ``unique_name`` identifies the
        values to plot.
    versus : object | None, default=None
        A diffrn descriptor (e.g.
        ``expt.diffrn.ambient_temperature``) whose value is used as
        the x-axis for each experiment.  When ``None``, the
        experiment sequence number is used instead.
    """
    unique_name = param.unique_name
    versus_name = versus.name if versus is not None else None
    self.plotter.plot_param_series(
        unique_name,
        versus_name,
        self.experiments,
        self.analysis._parameter_snapshots,
    )

plotter property

Plotting facade bound to the project.

save()

Save the project into the existing project directory.

Source code in src/easydiffraction/project/project.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def save(self) -> None:
    """Save the project into the existing project directory."""
    if self._info.path is None:
        log.error('Project path not specified. Use save_as() to define the path first.')
        return

    console.paragraph(f"Saving project 📦 '{self.name}' to")
    console.print(self.info.path.resolve())

    # Ensure project directory exists
    self._info.path.mkdir(parents=True, exist_ok=True)

    # Save project info
    with (self._info.path / 'project.cif').open('w') as f:
        f.write(self._info.as_cif())
        console.print('├── 📄 project.cif')

    # Save structures
    sm_dir = self._info.path / 'structures'
    sm_dir.mkdir(parents=True, exist_ok=True)
    console.print('├── 📁 structures/')
    for structure in self.structures.values():
        file_name: str = f'{structure.name}.cif'
        file_path = sm_dir / file_name
        with file_path.open('w') as f:
            f.write(structure.as_cif)
            console.print(f'│   └── 📄 {file_name}')

    # Save experiments
    expt_dir = self._info.path / 'experiments'
    expt_dir.mkdir(parents=True, exist_ok=True)
    console.print('├── 📁 experiments/')
    for experiment in self.experiments.values():
        file_name: str = f'{experiment.name}.cif'
        file_path = expt_dir / file_name
        with file_path.open('w') as f:
            f.write(experiment.as_cif)
            console.print(f'│   └── 📄 {file_name}')

    # Save analysis
    with (self._info.path / 'analysis.cif').open('w') as f:
        f.write(self.analysis.as_cif())
        console.print('├── 📄 analysis.cif')

    # Save summary
    with (self._info.path / 'summary.cif').open('w') as f:
        f.write(self.summary.as_cif())
        console.print('└── 📄 summary.cif')

    self._info.update_last_modified()
    self._saved = True

save_as(dir_path, temporary=False)

Save the project into a new directory.

Source code in src/easydiffraction/project/project.py
236
237
238
239
240
241
242
243
244
245
246
def save_as(
    self,
    dir_path: str,
    temporary: bool = False,
) -> None:
    """Save the project into a new directory."""
    if temporary:
        tmp: str = tempfile.gettempdir()
        dir_path = pathlib.Path(tmp) / dir_path
    self._info.path = dir_path
    self.save()

structures property writable

Collection of structures in the project.

summary property

Summary report builder bound to the project.

tabler property

Tables rendering facade bound to the project.

verbosity property writable

Project-wide console output verbosity.

Returns:

Type Description
str

One of 'full', 'short', or 'silent'.

project_info

Project metadata container used by Project.

ProjectInfo

Bases: GuardedBase

Store project metadata: name, title, description, paths.

Source code in src/easydiffraction/project/project_info.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class ProjectInfo(GuardedBase):
    """Store project metadata: name, title, description, paths."""

    def __init__(
        self,
        name: str = 'untitled_project',
        title: str = 'Untitled Project',
        description: str = '',
    ) -> None:
        super().__init__()

        self._name = name
        self._title = title
        self._description = description
        self._path: pathlib.Path | None = None  # pathlib.Path.cwd()
        self._created: datetime.datetime = datetime.datetime.now()
        self._last_modified: datetime.datetime = datetime.datetime.now()

    @property
    def name(self) -> str:
        """Return the project name."""
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        """
        Set the project name.

        Parameters
        ----------
        value : str
            New project name.
        """
        self._name = value

    @property
    def unique_name(self) -> str:
        """Unique name for GuardedBase diagnostics."""
        return self.name

    @property
    def title(self) -> str:
        """Return the project title."""
        return self._title

    @title.setter
    def title(self, value: str) -> None:
        """
        Set the project title.

        Parameters
        ----------
        value : str
            New project title.
        """
        self._title = value

    @property
    def description(self) -> str:
        """Return sanitized description with single spaces."""
        return ' '.join(self._description.split())

    @description.setter
    def description(self, value: str) -> None:
        """
        Set the project description (whitespace normalized).

        Parameters
        ----------
        value : str
            New description text.
        """
        self._description = ' '.join(value.split())

    @property
    def path(self) -> pathlib.Path | None:
        """Return the project path as a Path object."""
        return self._path

    @path.setter
    def path(self, value: object) -> None:
        """
        Set the project directory path.

        Parameters
        ----------
        value : object
            New path as a :class:`str` or :class:`pathlib.Path`.
        """
        # Accept str or Path; normalize to Path
        self._path = pathlib.Path(value)

    @property
    def created(self) -> datetime.datetime:
        """Return the creation timestamp."""
        return self._created

    @property
    def last_modified(self) -> datetime.datetime:
        """Return the last modified timestamp."""
        return self._last_modified

    def update_last_modified(self) -> None:
        """Update the last modified timestamp."""
        self._last_modified = datetime.datetime.now()

    def parameters(self) -> None:
        """List parameters (not implemented)."""
        pass

    # TODO: Consider moving to io.cif.serialize
    def as_cif(self) -> str:
        """Export project metadata to CIF."""
        return project_info_to_cif(self)

    # TODO: Consider moving to io.cif.serialize
    def show_as_cif(self) -> None:
        """Pretty-print CIF via shared utilities."""
        paragraph_title: str = f"Project 📦 '{self.name}' info as CIF"
        cif_text: str = self.as_cif()
        console.paragraph(paragraph_title)
        render_cif(cif_text)

as_cif()

Export project metadata to CIF.

Source code in src/easydiffraction/project/project_info.py
125
126
127
def as_cif(self) -> str:
    """Export project metadata to CIF."""
    return project_info_to_cif(self)

created property

Return the creation timestamp.

description property writable

Return sanitized description with single spaces.

last_modified property

Return the last modified timestamp.

name property writable

Return the project name.

parameters()

List parameters (not implemented).

Source code in src/easydiffraction/project/project_info.py
120
121
122
def parameters(self) -> None:
    """List parameters (not implemented)."""
    pass

path property writable

Return the project path as a Path object.

show_as_cif()

Pretty-print CIF via shared utilities.

Source code in src/easydiffraction/project/project_info.py
130
131
132
133
134
135
def show_as_cif(self) -> None:
    """Pretty-print CIF via shared utilities."""
    paragraph_title: str = f"Project 📦 '{self.name}' info as CIF"
    cif_text: str = self.as_cif()
    console.paragraph(paragraph_title)
    render_cif(cif_text)

title property writable

Return the project title.

unique_name property

Unique name for GuardedBase diagnostics.

update_last_modified()

Update the last modified timestamp.

Source code in src/easydiffraction/project/project_info.py
116
117
118
def update_last_modified(self) -> None:
    """Update the last modified timestamp."""
    self._last_modified = datetime.datetime.now()